Provide completion for LaTeX kernel environments

This commit is contained in:
Patrick Förster 2019-04-28 09:50:42 +02:00
parent 69c3f2335a
commit 574f448942
6 changed files with 259 additions and 1 deletions

View file

@ -1,6 +1,7 @@
use crate::feature::FeatureRequest;
use crate::range;
use crate::syntax::latex::analysis::command::LatexCommandAnalyzer;
use crate::syntax::latex::analysis::environment::ENVIRONMENT_COMMANDS;
use crate::syntax::latex::analysis::finder::{LatexFinder, LatexNode};
use crate::syntax::latex::ast::*;
use crate::syntax::text::SyntaxNode;
use crate::workspace::SyntaxTree;
@ -26,6 +27,78 @@ impl LatexCombinators {
}
Vec::new()
}
pub async fn argument<'a, E, F>(
request: &'a FeatureRequest<CompletionParams>,
command_names: &'a [&'a str],
argument_index: usize,
execute: E,
) -> Vec<CompletionItem>
where
E: Fn(&LatexCommand) -> F,
F: std::future::Future<Output = Vec<CompletionItem>>,
{
let find_command = |nodes: &[LatexNode<'a>], node_index: usize| {
if let LatexNode::Group(group) = nodes[node_index] {
if let LatexNode::Command(command) = nodes[node_index + 1] {
if command_names.contains(&command.name.text())
&& command.args.len() > argument_index
&& command.args[argument_index] == *group
{
return Some(command);
}
}
}
None
};
let find_non_empty_command = |nodes: &[LatexNode<'a>]| {
if nodes.len() >= 3 {
if let LatexNode::Text(_) = nodes[0] {
return find_command(nodes, 1);
}
}
None
};
let find_empty_command = |nodes: &[LatexNode<'a>]| {
if nodes.len() >= 2 {
find_command(nodes, 0)
} else {
None
}
};
if let SyntaxTree::Latex(tree) = &request.document.tree {
let mut finder = LatexFinder::new(request.params.position);
finder.visit_root(&tree.root);
let mut nodes = finder.results;
nodes.reverse();
let command = find_non_empty_command(&nodes).or_else(|| find_empty_command(&nodes));
if let Some(command) = command {
if range::contains_exclusive(
command.args[argument_index].range(),
request.params.position,
) {
return await!(execute(command));
}
}
}
Vec::new()
}
pub async fn environment<E, F>(
request: &FeatureRequest<CompletionParams>,
execute: E,
) -> Vec<CompletionItem>
where
E: Fn(&LatexCommand) -> F,
F: std::future::Future<Output = Vec<CompletionItem>>,
{
await!(Self::argument(&request, &ENVIRONMENT_COMMANDS, 0, execute))
}
}
struct LatexCommandFinder<'a> {

View file

@ -0,0 +1,89 @@
use crate::completion::factory;
use crate::completion::factory::LatexComponentId;
use crate::completion::latex::combinators::LatexCombinators;
use crate::completion::latex::kernel_primitives::KERNEL_ENVIRONMENTS;
use crate::feature::FeatureRequest;
use lsp_types::{CompletionItem, CompletionParams};
pub struct LatexKernelEnvironmentCompletionProvider;
impl LatexKernelEnvironmentCompletionProvider {
pub async fn execute(request: &FeatureRequest<CompletionParams>) -> Vec<CompletionItem> {
await!(LatexCombinators::environment(&request, async move |_| {
KERNEL_ENVIRONMENTS
.iter()
.map(|name| {
factory::create_environment((*name).to_owned(), LatexComponentId::Kernel)
})
.collect()
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::feature::FeatureTester;
use crate::workspace::WorkspaceBuilder;
use futures::executor;
#[test]
fn test_inside_of_empty_begin() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\begin{}");
let request = FeatureTester::new(builder.workspace, uri, 0, 7, "").into();
let items = executor::block_on(LatexKernelEnvironmentCompletionProvider::execute(&request));
assert_eq!(true, items.iter().any(|item| item.label == "document"));
}
#[test]
fn test_inside_of_nonempty_end() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\end{foo}");
let request = FeatureTester::new(builder.workspace, uri, 0, 6, "").into();
let items = executor::block_on(LatexKernelEnvironmentCompletionProvider::execute(&request));
assert_eq!(true, items.iter().any(|item| item.label == "document"));
}
#[test]
fn test_outside_of_empty_begin() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\begin{}");
let request = FeatureTester::new(builder.workspace, uri, 0, 6, "").into();
let items = executor::block_on(LatexKernelEnvironmentCompletionProvider::execute(&request));
assert_eq!(items, Vec::new());
}
#[test]
fn test_outside_of_empty_end() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\end{}");
let request = FeatureTester::new(builder.workspace, uri, 0, 6, "").into();
let items = executor::block_on(LatexKernelEnvironmentCompletionProvider::execute(&request));
assert_eq!(items, Vec::new());
}
#[test]
fn test_inside_of_other_command() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\foo{bar}");
let request = FeatureTester::new(builder.workspace, uri, 0, 6, "").into();
let items = executor::block_on(LatexKernelEnvironmentCompletionProvider::execute(&request));
assert_eq!(items, Vec::new());
}
#[test]
fn test_inside_second_argument() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\begin{foo}{bar}");
let request = FeatureTester::new(builder.workspace, uri, 0, 14, "").into();
let items = executor::block_on(LatexKernelEnvironmentCompletionProvider::execute(&request));
assert_eq!(items, Vec::new());
}
}

View file

@ -1928,3 +1928,43 @@ pub const KERNEL_COMMANDS: &'static [&'static str] = &[
"}",
"~",
];
pub const KERNEL_ENVIRONMENTS: &'static [&'static str] = &[
"abstract",
"array",
"center",
"csname",
"description",
"displaymath",
"document",
"enumerate",
"eqnarray",
"eqnarray*",
"equation",
"equation*",
"figure",
"filecontents",
"flushleft",
"flushright",
"input",
"itemize",
"line",
"list",
"lrbox",
"math",
"minipage",
"picture",
"quotation",
"quote",
"sloppypar",
"tabbing",
"table",
"tabular",
"tabular*",
"thebibliography",
"titlepage",
"trivlist",
"verbatim",
"verbatim*",
"verse",
];

View file

@ -1,3 +1,4 @@
mod combinators;
mod kernel_command;
mod kernel_environment;
mod kernel_primitives;

View file

@ -0,0 +1,54 @@
use crate::range;
use crate::syntax::latex::ast::*;
use crate::syntax::text::SyntaxNode;
use lsp_types::Position;
pub enum LatexNode<'a> {
Root(&'a LatexRoot),
Group(&'a LatexGroup),
Command(&'a LatexCommand),
Text(&'a LatexText),
}
pub struct LatexFinder<'a> {
pub position: Position,
pub results: Vec<LatexNode<'a>>,
}
impl<'a> LatexFinder<'a> {
pub fn new(position: Position) -> Self {
LatexFinder {
position,
results: Vec::new(),
}
}
}
impl<'a> LatexVisitor<'a> for LatexFinder<'a> {
fn visit_root(&mut self, root: &'a LatexRoot) {
if range::contains(root.range(), self.position) {
self.results.push(LatexNode::Root(root));
LatexWalker::walk_root(self, root);
}
}
fn visit_group(&mut self, group: &'a LatexGroup) {
if range::contains(group.range(), self.position) {
self.results.push(LatexNode::Group(group));
LatexWalker::walk_group(self, group);
}
}
fn visit_command(&mut self, command: &'a LatexCommand) {
if range::contains(command.range(), self.position) {
self.results.push(LatexNode::Command(command));
LatexWalker::walk_command(self, command);
}
}
fn visit_text(&mut self, text: &'a LatexText) {
if range::contains(text.range(), self.position) {
self.results.push(LatexNode::Text(text));
}
}
}

View file

@ -2,6 +2,7 @@ pub mod citation;
pub mod command;
pub mod environment;
pub mod equation;
pub mod finder;
pub mod include;
pub mod label;
pub mod section;