summaryrefslogtreecommitdiff
path: root/support/texlab/src/completion/latex
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/completion/latex')
-rw-r--r--support/texlab/src/completion/latex/argument.rs282
-rw-r--r--support/texlab/src/completion/latex/begin_cmd.rs70
-rw-r--r--support/texlab/src/completion/latex/begin_command.rs30
-rw-r--r--support/texlab/src/completion/latex/citation.rs371
-rw-r--r--support/texlab/src/completion/latex/color.rs139
-rw-r--r--support/texlab/src/completion/latex/color_model.rs169
-rw-r--r--support/texlab/src/completion/latex/combinators.rs169
-rw-r--r--support/texlab/src/completion/latex/component.rs557
-rw-r--r--support/texlab/src/completion/latex/glossary.rs149
-rw-r--r--support/texlab/src/completion/latex/import.rs244
-rw-r--r--support/texlab/src/completion/latex/include.rs190
-rw-r--r--support/texlab/src/completion/latex/label.rs338
-rw-r--r--support/texlab/src/completion/latex/mod.rs6
-rw-r--r--support/texlab/src/completion/latex/theorem.rs163
-rw-r--r--support/texlab/src/completion/latex/tikz.rs90
-rw-r--r--support/texlab/src/completion/latex/tikz_lib.rs138
-rw-r--r--support/texlab/src/completion/latex/user.rs324
17 files changed, 2065 insertions, 1364 deletions
diff --git a/support/texlab/src/completion/latex/argument.rs b/support/texlab/src/completion/latex/argument.rs
index db05bca824..5736099ca6 100644
--- a/support/texlab/src/completion/latex/argument.rs
+++ b/support/texlab/src/completion/latex/argument.rs
@@ -1,44 +1,204 @@
use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::completion::DATABASE;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::*;
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+};
use std::iter;
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub async fn complete_latex_arguments<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ for comp in req.view.components() {
+ for cmd in &comp.commands {
+ for (i, param) in cmd.parameters.iter().enumerate() {
+ complete_internal(req, items, &cmd.name, i, param).await;
+ }
+ }
+ }
+}
+
+async fn complete_internal<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+ name: &'a str,
+ index: usize,
+ param: &'a crate::components::Parameter,
+) {
+ combinators::argument(
+ req,
+ iter::once(Parameter { name, index }),
+ |ctx| async move {
+ for arg in &param.0 {
+ let item = Item::new(
+ ctx.range,
+ ItemData::Argument {
+ name: &arg.name,
+ image: arg.image.as_deref(),
+ },
+ );
+ items.push(item);
+ }
+ },
+ )
+ .await;
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ feature::FeatureTester,
+ protocol::{Range, RangeExt},
+ };
+ use indoc::indoc;
+
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_arguments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_arguments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn inside_mathbb_empty() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \usepackage{amsfonts}
+ \mathbb{}
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 8)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_arguments(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 8, 1, 8));
+ }
+
+ #[tokio::test]
+ async fn inside_mathbb_non_empty() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \usepackage{amsfonts}
+ \mathbb{foo}
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 8)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_arguments(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 8, 1, 11));
+ }
+
+ #[tokio::test]
+ async fn outside_mathbb_empty() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \usepackage{amsfonts}
+ \mathbb{}
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 9)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_arguments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+}
+
+/*
+use super::combinators::{self, Parameter};
+use crate::{
+ completion::factory,
+ feature::{FeatureProvider, FeatureRequest},
+ protocol::{CompletionItem, CompletionParams, TextEdit},
+};
+use async_trait::async_trait;
+use std::iter;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub struct LatexArgumentCompletionProvider;
+#[async_trait]
impl FeatureProvider for LatexArgumentCompletionProvider {
type Params = CompletionParams;
type Output = Vec<CompletionItem>;
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
+ async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output {
let mut all_items = Vec::new();
- for component in DATABASE.related_components(request.related_documents()) {
- for command in &component.commands {
- let name = format!("\\{}", command.name);
- for (i, parameter) in command.parameters.iter().enumerate() {
+ for comp in req.view.components() {
+ for cmd in &comp.commands {
+ let name = format!("\\{}", cmd.name);
+ for (i, param) in cmd.parameters.iter().enumerate() {
let mut items = combinators::argument(
- request,
- iter::once(Parameter::new(&name, i)),
- |context| {
- async move {
- let mut items = Vec::new();
- for argument in &parameter.0 {
- let text_edit =
- TextEdit::new(context.range, (&argument.name).into());
- let item = factory::argument(
- request,
- &argument.name,
- text_edit,
- argument.image.as_ref().map(AsRef::as_ref),
- );
- items.push(item);
- }
- items
+ req,
+ iter::once(Parameter {
+ name: &name,
+ index: i,
+ }),
+ |ctx| async move {
+ let mut items = Vec::new();
+ for arg in &param.0 {
+ let text_edit = TextEdit::new(ctx.range, (&arg.name).into());
+ let item = factory::argument(
+ req,
+ &arg.name,
+ text_edit,
+ arg.image.as_ref().map(AsRef::as_ref),
+ );
+ items.push(item);
}
+ items
},
)
.await;
@@ -50,68 +210,6 @@ impl FeatureProvider for LatexArgumentCompletionProvider {
}
}
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::range::RangeExt;
- use lsp_types::{Position, Range};
-
- #[test]
- fn test_inside_mathbb_empty() {
- let items = test_feature(
- LatexArgumentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file(
- "foo.tex",
- "\\usepackage{amsfonts}\n\\mathbb{}",
- )],
- main_file: "foo.tex",
- position: Position::new(1, 8),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(1, 8, 1, 8))
- );
- }
- #[test]
- fn test_inside_mathbb_non_empty() {
- let items = test_feature(
- LatexArgumentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file(
- "foo.tex",
- "\\usepackage{amsfonts}\n\\mathbb{foo}",
- )],
- main_file: "foo.tex",
- position: Position::new(1, 8),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(1, 8, 1, 11))
- );
- }
- #[test]
- fn test_outside_mathbb() {
- let items = test_feature(
- LatexArgumentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file(
- "foo.tex",
- "\\usepackage{amsfonts}\n\\mathbb{}",
- )],
- main_file: "foo.tex",
- position: Position::new(1, 9),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
- }
-}
+*/
diff --git a/support/texlab/src/completion/latex/begin_cmd.rs b/support/texlab/src/completion/latex/begin_cmd.rs
new file mode 100644
index 0000000000..44e7a4f60d
--- /dev/null
+++ b/support/texlab/src/completion/latex/begin_cmd.rs
@@ -0,0 +1,70 @@
+use super::combinators;
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+};
+
+pub async fn complete_latex_begin_command<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ combinators::command(req, |cmd_node| async move {
+ let table = req.current().content.as_latex().unwrap();
+ let cmd = table.as_command(cmd_node).unwrap();
+ let range = cmd.short_name_range();
+ items.push(Item::new(range, ItemData::BeginCommand));
+ })
+ .await;
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::feature::FeatureTester;
+
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_begin_command(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_begin_command(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn after_backslash() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\"#)
+ .main("main.tex")
+ .position(0, 1)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_begin_command(&req, &mut actual_items).await;
+
+ assert_eq!(actual_items.len(), 1);
+ }
+}
diff --git a/support/texlab/src/completion/latex/begin_command.rs b/support/texlab/src/completion/latex/begin_command.rs
deleted file mode 100644
index ce6ba17afa..0000000000
--- a/support/texlab/src/completion/latex/begin_command.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use super::combinators;
-use crate::completion::factory::{self, LatexComponentId};
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexBeginCommandCompletionProvider;
-
-impl FeatureProvider for LatexBeginCommandCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- combinators::command(request, |_| {
- async move {
- let snippet = factory::command_snippet(
- request,
- "begin",
- None,
- "begin{$1}\n\t$0\n\\end{$1}",
- &LatexComponentId::kernel(),
- );
- vec![snippet]
- }
- })
- .await
- }
-}
diff --git a/support/texlab/src/completion/latex/citation.rs b/support/texlab/src/completion/latex/citation.rs
index a217c24bff..3e7b1851d1 100644
--- a/support/texlab/src/completion/latex/citation.rs
+++ b/support/texlab/src/completion/latex/citation.rs
@@ -1,144 +1,261 @@
-use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, TextEdit};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexCitationCompletionProvider;
-
-impl FeatureProvider for LatexCitationCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameters = LANGUAGE_DATA
- .citation_commands
- .iter()
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
-
- combinators::argument(request, parameters, |context| {
- async move {
- let mut items = Vec::new();
- for document in request.related_documents() {
- if let SyntaxTree::Bibtex(tree) = &document.tree {
- for entry in &tree.entries() {
- if !entry.is_comment() {
- if let Some(key) = &entry.key {
- let key = key.text().to_owned();
- let text_edit = TextEdit::new(context.range, key.clone());
- let item = factory::citation(
- request,
- document.uri.clone(),
- entry,
- key,
- text_edit,
- );
- items.push(item);
- }
- }
- }
+use super::combinators::{self, ArgumentContext, Parameter};
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::{BibtexFormattingOptions, CompletionParams},
+ syntax::{bibtex, BibtexEntryTypeCategory, Structure, LANGUAGE_DATA},
+ workspace::{Document, DocumentContent},
+};
+use once_cell::sync::Lazy;
+use petgraph::graph::NodeIndex;
+use regex::Regex;
+
+pub async fn complete_latex_citations<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let parameters = LANGUAGE_DATA.citation_commands.iter().map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
+
+ combinators::argument(req, parameters, |ctx| async move {
+ for doc in req.related() {
+ if let DocumentContent::Bibtex(tree) = &doc.content {
+ for entry_node in tree.children(tree.root) {
+ if let Some(item) = make_item(ctx, doc, tree, entry_node) {
+ items.push(item);
}
}
- items
}
- })
- .await
+ }
+ })
+ .await;
+}
+
+fn make_item<'a>(
+ ctx: ArgumentContext,
+ doc: &'a Document,
+ tree: &'a bibtex::Tree,
+ entry_node: NodeIndex,
+) -> Option<Item<'a>> {
+ let entry = tree.as_entry(entry_node)?;
+ if entry.is_comment() {
+ return None;
}
+
+ let key = entry.key.as_ref()?.text();
+ let options = BibtexFormattingOptions::default();
+ let params = bibtex::FormattingParams {
+ insert_spaces: true,
+ tab_size: 4,
+ options: &options,
+ };
+ let entry_code = bibtex::format(tree, entry_node, params);
+ let text = format!(
+ "{} {}",
+ &key,
+ WHITESPACE_REGEX
+ .replace_all(
+ &entry_code
+ .replace('{', "")
+ .replace('}', "")
+ .replace(',', " ")
+ .replace('=', " "),
+ " ",
+ )
+ .trim()
+ );
+
+ let ty = LANGUAGE_DATA
+ .find_entry_type(&entry.ty.text()[1..])
+ .map(|ty| Structure::Entry(ty.category))
+ .unwrap_or_else(|| Structure::Entry(BibtexEntryTypeCategory::Misc));
+
+ let item = Item::new(
+ ctx.range,
+ ItemData::Citation {
+ uri: &doc.uri,
+ key,
+ text,
+ ty,
+ },
+ );
+ Some(item)
}
+static WHITESPACE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\s+").unwrap());
+
#[cfg(test)]
mod tests {
use super::*;
- use crate::range::RangeExt;
- use lsp_types::{Position, Range};
-
- #[test]
- fn test_empty() {
- let items = test_feature(
- LatexCitationCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file("foo.tex", "\\addbibresource{bar.bib}\n\\cite{}"),
- FeatureSpec::file("bar.bib", "@article{foo,}"),
- FeatureSpec::file("baz.bib", "@article{bar,}"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 6),
- ..FeatureSpec::default()
- },
- );
- assert_eq!(items.len(), 1);
- assert_eq!(items[0].label, "foo");
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(1, 6, 1, 6))
- );
+ use crate::{
+ feature::FeatureTester,
+ protocol::{Range, RangeExt},
+ };
+ use indoc::indoc;
+
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_citations(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_single_key() {
- let items = test_feature(
- LatexCitationCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file("foo.tex", "\\addbibresource{bar.bib}\n\\cite{foo}"),
- FeatureSpec::file("bar.bib", "@article{foo,}"),
- FeatureSpec::file("baz.bib", "@article{bar,}"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 6),
- ..FeatureSpec::default()
- },
- );
- assert_eq!(items.len(), 1);
- assert_eq!(items[0].label, "foo");
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(1, 6, 1, 9))
- );
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_citations(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_second_key() {
- let items = test_feature(
- LatexCitationCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file("foo.tex", "\\addbibresource{bar.bib}\n\\cite{foo,}"),
- FeatureSpec::file("bar.bib", "@article{foo,}"),
- FeatureSpec::file("baz.bib", "@article{bar,}"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 10),
- ..FeatureSpec::default()
- },
- );
- assert_eq!(items.len(), 1);
- assert_eq!(items[0].label, "foo");
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(1, 10, 1, 10))
- );
+ #[tokio::test]
+ async fn incomplete() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \addbibresource{main.bib}
+ \cite{
+ \begin{foo}
+ \end{bar}
+ "#
+ ),
+ )
+ .file("main.bib", "@article{foo,}")
+ .main("main.tex")
+ .position(1, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_citations(&req, &mut actual_items).await;
+
+ assert_eq!(actual_items.len(), 1);
+ assert_eq!(actual_items[0].data.label(), "foo");
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 6, 1, 6));
}
- #[test]
- fn test_outside_cite() {
- let items = test_feature(
- LatexCitationCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file("foo.tex", "\\addbibresource{bar.bib}\n\\cite{}"),
- FeatureSpec::file("bar.bib", "@article{foo,}"),
- FeatureSpec::file("baz.bib", "@article{bar,}"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 7),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn empty_key() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \addbibresource{bar.bib}
+ \cite{}
+ "#
+ ),
+ )
+ .file("bar.bib", "@article{foo,}")
+ .file("baz.bib", "@article{bar,}")
+ .main("foo.tex")
+ .position(1, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_citations(&req, &mut actual_items).await;
+
+ assert_eq!(actual_items.len(), 1);
+ assert_eq!(actual_items[0].data.label(), "foo");
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 6, 1, 6));
+ }
+
+ #[tokio::test]
+ async fn single_key() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \addbibresource{bar.bib}
+ \cite{foo}
+ "#
+ ),
+ )
+ .file("bar.bib", "@article{foo,}")
+ .file("baz.bib", "@article{bar,}")
+ .main("foo.tex")
+ .position(1, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_citations(&req, &mut actual_items).await;
+
+ assert_eq!(actual_items.len(), 1);
+ assert_eq!(actual_items[0].data.label(), "foo");
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 6, 1, 9));
+ }
+
+ #[tokio::test]
+ async fn second_key() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \addbibresource{bar.bib}
+ \cite{foo,}
+ "#
+ ),
+ )
+ .file("bar.bib", "@article{foo,}")
+ .file("baz.bib", "@article{bar,}")
+ .main("foo.tex")
+ .position(1, 10)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_citations(&req, &mut actual_items).await;
+
+ assert_eq!(actual_items.len(), 1);
+ assert_eq!(actual_items[0].data.label(), "foo");
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 10, 1, 10));
+ }
+
+ #[tokio::test]
+ async fn outside_cite() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \addbibresource{bar.bib}
+ \cite{}
+ "#
+ ),
+ )
+ .file("bar.bib", "@article{foo,}")
+ .file("baz.bib", "@article{bar,}")
+ .main("foo.tex")
+ .position(1, 7)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_citations(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
}
diff --git a/support/texlab/src/completion/latex/color.rs b/support/texlab/src/completion/latex/color.rs
index b2d390b7d8..93b06b178e 100644
--- a/support/texlab/src/completion/latex/color.rs
+++ b/support/texlab/src/completion/latex/color.rs
@@ -1,74 +1,91 @@
use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, TextEdit};
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+ syntax::LANGUAGE_DATA,
+};
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexColorCompletionProvider;
+pub async fn complete_latex_colors<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let parameters = LANGUAGE_DATA.color_commands.iter().map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
-impl FeatureProvider for LatexColorCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameters = LANGUAGE_DATA
- .color_commands
- .iter()
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
-
- combinators::argument(request, parameters, |context| {
- async move {
- let mut items = Vec::new();
- for name in &LANGUAGE_DATA.colors {
- let text_edit = TextEdit::new(context.range, name.into());
- let item = factory::color(request, name, text_edit);
- items.push(item);
- }
- items
- }
- })
- .await
- }
+ combinators::argument(req, parameters, |ctx| async move {
+ for name in &LANGUAGE_DATA.colors {
+ let item = Item::new(ctx.range, ItemData::Color { name });
+ items.push(item);
+ }
+ })
+ .await;
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::range::RangeExt;
- use lsp_types::{Position, Range};
+ use crate::{
+ feature::FeatureTester,
+ protocol::{Range, RangeExt},
+ };
- #[test]
- fn test_inside_color() {
- let items = test_feature(
- LatexColorCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\color{}")],
- main_file: "foo.tex",
- position: Position::new(0, 7),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(0, 7, 0, 7))
- );
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_colors(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_outside_color() {
- let items = test_feature(
- LatexColorCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\color{}")],
- main_file: "foo.tex",
- position: Position::new(0, 8),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_colors(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn inside_color() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\color{}"#)
+ .main("main.tex")
+ .position(0, 7)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_colors(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(0, 7, 0, 7));
+ }
+
+ #[tokio::test]
+ async fn inside_define_color_set() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\color{}"#)
+ .main("main.tex")
+ .position(0, 8)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_colors(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
}
diff --git a/support/texlab/src/completion/latex/color_model.rs b/support/texlab/src/completion/latex/color_model.rs
index 5b6e85fa31..e0151da2f0 100644
--- a/support/texlab/src/completion/latex/color_model.rs
+++ b/support/texlab/src/completion/latex/color_model.rs
@@ -1,94 +1,101 @@
use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::syntax::LANGUAGE_DATA;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, TextEdit};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexColorModelCompletionProvider;
-
-impl FeatureProvider for LatexColorModelCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameters = LANGUAGE_DATA
- .color_model_commands
- .iter()
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
-
- combinators::argument(&request, parameters, |context| {
- async move {
- let mut items = Vec::new();
- for name in MODEL_NAMES {
- let text_edit = TextEdit::new(context.range, (*name).into());
- let item = factory::color_model(request, name, text_edit);
- items.push(item);
- }
- items
- }
- })
- .await
- }
-}
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+ syntax::LANGUAGE_DATA,
+};
const MODEL_NAMES: &[&str] = &["gray", "rgb", "RGB", "HTML", "cmyk"];
+pub async fn complete_latex_color_models<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let parameters = LANGUAGE_DATA
+ .color_model_commands
+ .iter()
+ .map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
+
+ combinators::argument(req, parameters, |ctx| async move {
+ for name in MODEL_NAMES {
+ let item = Item::new(ctx.range, ItemData::ColorModel { name });
+ items.push(item);
+ }
+ })
+ .await;
+}
+
#[cfg(test)]
mod tests {
use super::*;
- use crate::range::RangeExt;
- use lsp_types::{Position, Range};
-
- #[test]
- fn test_inside_define_color() {
- let items = test_feature(
- LatexColorModelCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\definecolor{name}{}")],
- main_file: "foo.tex",
- position: Position::new(0, 19),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(0, 19, 0, 19))
- );
+ use crate::{
+ feature::FeatureTester,
+ protocol::{Range, RangeExt},
+ };
+
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_color_models(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_outside_define_color() {
- let items = test_feature(
- LatexColorModelCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\definecolor{name}{}")],
- main_file: "foo.tex",
- position: Position::new(0, 18),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_color_models(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn inside_define_color() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\definecolor{name}{}"#)
+ .main("main.tex")
+ .position(0, 19)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_color_models(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(0, 19, 0, 19));
}
- #[test]
- fn tet_inside_define_color_set() {
- let items = test_feature(
- LatexColorModelCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\definecolorset{}")],
- main_file: "foo.tex",
- position: Position::new(0, 16),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(0, 16, 0, 16))
- );
+ #[tokio::test]
+ async fn inside_define_color_set() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\definecolorset{}"#)
+ .main("main.tex")
+ .position(0, 16)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_color_models(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(0, 16, 0, 16));
}
}
diff --git a/support/texlab/src/completion/latex/combinators.rs b/support/texlab/src/completion/latex/combinators.rs
index 74337f85b6..d8ef336421 100644
--- a/support/texlab/src/completion/latex/combinators.rs
+++ b/support/texlab/src/completion/latex/combinators.rs
@@ -1,9 +1,10 @@
-use crate::range::RangeExt;
-use crate::syntax::*;
-use crate::workspace::*;
-use lsp_types::*;
+use crate::{
+ feature::FeatureRequest,
+ protocol::{CompletionParams, Position, Range, RangeExt},
+ syntax::{latex, AstNodeIndex, SyntaxNode, LANGUAGE_DATA},
+ workspace::DocumentContent,
+};
use std::future::Future;
-use std::sync::Arc;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Parameter<'a> {
@@ -11,142 +12,132 @@ pub struct Parameter<'a> {
pub index: usize,
}
-impl<'a> Parameter<'a> {
- pub fn new(name: &'a str, index: usize) -> Self {
- Self { name, index }
- }
-}
-
-pub async fn command<E, F>(
- request: &FeatureRequest<CompletionParams>,
- execute: E,
-) -> Vec<CompletionItem>
+pub async fn command<E, F>(req: &FeatureRequest<CompletionParams>, execute: E)
where
- E: FnOnce(Arc<LatexCommand>) -> F,
- F: Future<Output = Vec<CompletionItem>>,
+ E: FnOnce(AstNodeIndex) -> F,
+ F: Future<Output = ()>,
{
- if let SyntaxTree::Latex(tree) = &request.document().tree {
- if let Some(command) =
- tree.find_command_by_name(request.params.text_document_position.position)
- {
- return execute(command).await;
+ if let DocumentContent::Latex(table) = &req.current().content {
+ let pos = req.params.text_document_position.position;
+ if let Some(cmd) = table.find_command_by_short_name_range(pos) {
+ execute(cmd).await;
}
}
- Vec::new()
}
-#[derive(Debug, PartialEq, Eq, Clone)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ArgumentContext<'a> {
pub parameter: Parameter<'a>,
- pub command: Arc<LatexCommand>,
+ pub node: AstNodeIndex,
pub range: Range,
}
pub async fn argument<'a, I, E, F>(
- request: &'a FeatureRequest<CompletionParams>,
+ req: &'a FeatureRequest<CompletionParams>,
mut parameters: I,
execute: E,
-) -> Vec<CompletionItem>
-where
+) where
I: Iterator<Item = Parameter<'a>>,
E: FnOnce(ArgumentContext<'a>) -> F,
- F: Future<Output = Vec<CompletionItem>>,
+ F: Future<Output = ()>,
{
- if let SyntaxTree::Latex(tree) = &request.document().tree {
- let position = request.params.text_document_position.position;
- if let Some(command) = find_command(tree, position) {
- for parameter in parameters.by_ref() {
- if command.name.text() != parameter.name {
- continue;
- }
-
- if let Some(args) = command.args.get(parameter.index) {
- if args.right.is_some() && !args.range().contains_exclusive(position) {
+ if let DocumentContent::Latex(table) = &req.current().content {
+ let pos = req.params.text_document_position.position;
+ if let Some(node) = find_command(&table, pos) {
+ let cmd = table.as_command(node).unwrap();
+ for parameter in parameters
+ .by_ref()
+ .filter(|param| param.name == &cmd.name.text()[1..])
+ {
+ if let Some(args_node) =
+ table.extract_group(node, latex::GroupKind::Group, parameter.index)
+ {
+ let args = table.as_group(args_node).unwrap();
+ if args.right.is_some() && !args.range().contains_exclusive(pos) {
continue;
}
- let mut range = None;
- for child in &args.children {
- if let LatexContent::Text(text) = &child {
- for word in &text.words {
- if word.range().contains(position) {
- range = Some(word.range());
- break;
- }
- }
- }
- }
- let text_range = range.unwrap_or_else(|| Range::new(position, position));
+ let range = table
+ .children(args_node)
+ .filter_map(|child| table.as_text(child))
+ .flat_map(|text| text.words.iter())
+ .map(|word| word.range())
+ .find(|range| range.contains(pos))
+ .unwrap_or_else(|| Range::new(pos, pos));
+
let context = ArgumentContext {
parameter,
- command: Arc::clone(&command),
- range: text_range,
+ node,
+ range,
};
- return execute(context).await;
+ execute(context).await;
+ return;
}
}
}
}
- Vec::new()
}
pub async fn argument_word<'a, I, E, F>(
- request: &'a FeatureRequest<CompletionParams>,
+ req: &'a FeatureRequest<CompletionParams>,
mut parameters: I,
execute: E,
-) -> Vec<CompletionItem>
-where
+) where
I: Iterator<Item = Parameter<'a>>,
- E: FnOnce(Arc<LatexCommand>, usize) -> F,
- F: Future<Output = Vec<CompletionItem>>,
+ E: FnOnce(AstNodeIndex, usize) -> F,
+ F: Future<Output = ()>,
{
- if let SyntaxTree::Latex(tree) = &request.document().tree {
- let position = request.params.text_document_position.position;
- if let Some(command) = find_command(tree, position) {
- for parameter in parameters.by_ref() {
- if command.name.text() != parameter.name {
- continue;
- }
-
- if let Some(args) = command.args.get(parameter.index) {
- if args.right.is_some() && !args.range().contains_exclusive(position) {
+ if let DocumentContent::Latex(table) = &req.current().content {
+ let pos = req.params.text_document_position.position;
+ if let Some(node) = find_command(&table, pos) {
+ let cmd = table.as_command(node).unwrap();
+ for parameter in parameters
+ .by_ref()
+ .filter(|param| param.name == &cmd.name.text()[1..])
+ {
+ if let Some(args_node) =
+ table.extract_group(node, latex::GroupKind::Group, parameter.index)
+ {
+ let args = table.as_group(args_node).unwrap();
+ if args.right.is_some() && !args.range().contains_exclusive(pos) {
continue;
}
- if !args.children.is_empty() && !command.has_word(parameter.index) {
+ if table.children(args_node).next().is_some()
+ && table
+ .extract_word(node, latex::GroupKind::Group, parameter.index)
+ .is_none()
+ {
continue;
}
- return execute(Arc::clone(&command), parameter.index).await;
+ execute(node, parameter.index).await;
+ return;
}
}
}
}
- Vec::new()
}
-pub async fn environment<'a, E, F>(
- request: &'a FeatureRequest<CompletionParams>,
- execute: E,
-) -> Vec<CompletionItem>
+pub async fn environment<'a, E, F>(req: &'a FeatureRequest<CompletionParams>, execute: E)
where
E: FnOnce(ArgumentContext<'a>) -> F,
- F: Future<Output = Vec<CompletionItem>>,
+ F: Future<Output = ()>,
{
let parameters = LANGUAGE_DATA
.environment_commands
.iter()
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
- argument(request, parameters, execute).await
+ .map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
+ argument(req, parameters, execute).await;
}
-fn find_command(tree: &LatexSyntaxTree, position: Position) -> Option<Arc<LatexCommand>> {
- let mut nodes = tree.find(position);
- nodes.reverse();
- for node in nodes {
- if let LatexNode::Command(command) = node {
- return Some(command);
- }
- }
- None
+fn find_command(table: &latex::SymbolTable, pos: Position) -> Option<AstNodeIndex> {
+ table
+ .find(pos)
+ .into_iter()
+ .rev()
+ .find(|node| table.as_command(*node).is_some())
}
diff --git a/support/texlab/src/completion/latex/component.rs b/support/texlab/src/completion/latex/component.rs
index 3699a4959e..8cf9f494a4 100644
--- a/support/texlab/src/completion/latex/component.rs
+++ b/support/texlab/src/completion/latex/component.rs
@@ -1,278 +1,351 @@
use super::combinators;
-use crate::completion::factory::{self, LatexComponentId};
-use crate::completion::DATABASE;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::*;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexComponentCommandCompletionProvider;
-
-impl FeatureProvider for LatexComponentCommandCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- combinators::command(request, |command| {
- async move {
- let range = command.short_name_range();
- let mut items = Vec::new();
- for component in DATABASE.related_components(request.related_documents()) {
- let file_names = component.file_names.iter().map(AsRef::as_ref).collect();
- let id = LatexComponentId::Component(file_names);
- for command in &component.commands {
- let text_edit = TextEdit::new(range, (&command.name).into());
- let item = factory::command(
- request,
- (&command.name).into(),
- command.image.as_ref().map(AsRef::as_ref),
- command.glyph.as_ref().map(AsRef::as_ref),
- text_edit,
- &id,
- );
- items.push(item);
- }
- }
- items
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+};
+
+pub async fn complete_latex_component_commands<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ combinators::command(req, |cmd_node| async move {
+ let table = req.current().content.as_latex().unwrap();
+ let cmd = table.as_command(cmd_node).unwrap();
+ let range = cmd.short_name_range();
+
+ for comp in req.view.components() {
+ for cmd in &comp.commands {
+ items.push(Item::new(
+ range,
+ ItemData::ComponentCommand {
+ name: &cmd.name,
+ image: cmd.image.as_deref(),
+ glyph: cmd.glyph.as_deref(),
+ file_names: &comp.file_names,
+ },
+ ));
}
- })
- .await
- }
+ }
+ })
+ .await;
}
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexComponentEnvironmentCompletionProvider;
-
-impl FeatureProvider for LatexComponentEnvironmentCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- combinators::environment(request, |context| {
- async move {
- let mut items = Vec::new();
- for component in DATABASE.related_components(request.related_documents()) {
- let file_names = component.file_names.iter().map(AsRef::as_ref).collect();
- let id = LatexComponentId::Component(file_names);
- for environment in &component.environments {
- let text_edit = TextEdit::new(context.range, environment.into());
- let item =
- factory::environment(request, environment.into(), text_edit, &id);
- items.push(item);
- }
- }
- items
+pub async fn complete_latex_component_environments<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ combinators::environment(req, |ctx| async move {
+ for comp in req.view.components() {
+ for env in &comp.environments {
+ items.push(Item::new(
+ ctx.range,
+ ItemData::ComponentEnvironment {
+ name: env,
+ file_names: &comp.file_names,
+ },
+ ));
}
- })
- .await
- }
+ }
+ })
+ .await;
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::range::RangeExt;
- use lsp_types::{Position, Range};
-
- #[test]
- fn test_command_start() {
- let items = test_feature(
- LatexComponentCommandCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\use")],
- main_file: "foo.tex",
- position: Position::new(0, 0),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ use crate::{
+ feature::FeatureTester,
+ protocol::{Range, RangeExt},
+ };
+ use indoc::indoc;
+
+ #[tokio::test]
+ async fn empty_latex_document_command() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_command_end() {
- let items = test_feature(
- LatexComponentCommandCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\use")],
- main_file: "foo.tex",
- position: Position::new(0, 4),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(0, 1, 0, 4))
- );
+ #[tokio::test]
+ async fn empty_bibtex_document_command() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_command_word() {
- let items = test_feature(
- LatexComponentCommandCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "use")],
- main_file: "foo.tex",
- position: Position::new(0, 2),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn empty_latex_document_environment() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_command_package() {
- let items = test_feature(
- LatexComponentCommandCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\usepackage{lipsum}\n\\lips")],
- main_file: "foo.tex",
- position: Position::new(1, 2),
- ..FeatureSpec::default()
- },
- );
- assert!(items.iter().any(|item| item.label == "lipsum"));
+ #[tokio::test]
+ async fn empty_bibtex_document_environment() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_command_package_comma_separated() {
- let items = test_feature(
- LatexComponentCommandCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file(
- "foo.tex",
- "\\usepackage{geometry, lipsum}\n\\lips",
- )],
- main_file: "foo.tex",
- position: Position::new(1, 2),
- ..FeatureSpec::default()
- },
- );
- assert!(items.iter().any(|item| item.label == "lipsum"));
+ #[tokio::test]
+ async fn command_start() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\use"#)
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_command_class() {
- let items = test_feature(
- LatexComponentCommandCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file(
- "foo.tex",
- "\\documentclass{book}\n\\chap",
- )],
- main_file: "foo.tex",
- position: Position::new(1, 2),
- ..FeatureSpec::default()
- },
- );
- assert!(items.iter().any(|item| item.label == "chapter"));
+ #[tokio::test]
+ async fn command_end() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\use"#)
+ .main("main.tex")
+ .position(0, 4)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(0, 1, 0, 4));
}
- #[test]
- fn test_environment_inside_of_empty_begin() {
- let items = test_feature(
- LatexComponentEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\begin{}")],
- main_file: "foo.tex",
- position: Position::new(0, 7),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(0, 7, 0, 7))
- );
+ #[tokio::test]
+ async fn command_word() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"use"#)
+ .main("main.tex")
+ .position(0, 2)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_environment_inside_of_non_empty_end() {
- let items = test_feature(
- LatexComponentEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\end{foo}")],
- main_file: "foo.tex",
- position: Position::new(0, 6),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(0, 5, 0, 8))
- );
+ #[tokio::test]
+ async fn command_package() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \usepackage{lipsum}
+ \lips
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 2)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items
+ .iter()
+ .any(|item| item.data.label() == "lipsum"));
}
- #[test]
- fn test_environment_outside_of_empty_begin() {
- let items = test_feature(
- LatexComponentEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\begin{}")],
- main_file: "foo.tex",
- position: Position::new(0, 6),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn command_package_comma_separated() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \usepackage{geometry, lipsum}
+ \lips
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 2)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items
+ .iter()
+ .any(|item| item.data.label() == "lipsum"));
}
- #[test]
- fn test_environment_outside_of_empty_end() {
- let items = test_feature(
- LatexComponentEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\end{}")],
- main_file: "foo.tex",
- position: Position::new(0, 6),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn command_class() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \documentclass{book}
+ \chap
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 2)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items
+ .iter()
+ .any(|item| item.data.label() == "chapter"));
+ }
+
+ #[tokio::test]
+ async fn environment_inside_of_empty_begin() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\begin{}"#)
+ .main("main.tex")
+ .position(0, 7)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(0, 7, 0, 7));
}
- #[test]
- fn test_environment_inside_of_other_command() {
- let items = test_feature(
- LatexComponentEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\foo{bar}")],
- main_file: "foo.tex",
- position: Position::new(0, 6),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn environment_inside_of_non_empty_end() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\end{foo}"#)
+ .main("main.tex")
+ .position(0, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(0, 5, 0, 8));
}
- #[test]
- fn test_environment_inside_second_argument() {
- let items = test_feature(
- LatexComponentEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\begin{foo}{bar}")],
- main_file: "foo.tex",
- position: Position::new(0, 14),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn environment_outside_of_empty_begin() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\begin{}"#)
+ .main("main.tex")
+ .position(0, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_environment_unterminated() {
- let items = test_feature(
- LatexComponentEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\begin{ foo")],
- main_file: "foo.tex",
- position: Position::new(0, 7),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
+ #[tokio::test]
+ async fn environment_outside_of_empty_end() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\end{}"#)
+ .main("main.tex")
+ .position(0, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn environment_inside_of_other_command() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\foo{bar}"#)
+ .main("main.tex")
+ .position(0, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn environment_inside_second_argument() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\begin{foo}{bar}"#)
+ .main("main.tex")
+ .position(0, 14)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn environment_unterminated() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\begin{foo"#)
+ .main("main.tex")
+ .position(0, 7)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_component_environments(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ assert_eq!(actual_items[0].range, Range::new_simple(0, 7, 0, 10));
}
}
diff --git a/support/texlab/src/completion/latex/glossary.rs b/support/texlab/src/completion/latex/glossary.rs
index ca6600b690..417152dd21 100644
--- a/support/texlab/src/completion/latex/glossary.rs
+++ b/support/texlab/src/completion/latex/glossary.rs
@@ -1,53 +1,114 @@
use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::syntax::LatexGlossaryEntryKind::*;
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, TextEdit};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexGlossaryCompletionProvider;
-
-impl FeatureProvider for LatexGlossaryCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameters = LANGUAGE_DATA
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+ syntax::{
+ LatexGlossaryEntryKind::{Acronym, General},
+ LANGUAGE_DATA,
+ },
+ workspace::DocumentContent,
+};
+
+pub async fn complete_latex_glossary_entries<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let parameters = LANGUAGE_DATA
+ .glossary_entry_reference_commands
+ .iter()
+ .map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
+
+ combinators::argument(req, parameters, |ctx| async move {
+ let cmd_kind = LANGUAGE_DATA
.glossary_entry_reference_commands
.iter()
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
-
- combinators::argument(request, parameters, |context| {
- async move {
- let cmd_kind = LANGUAGE_DATA
- .glossary_entry_reference_commands
- .iter()
- .find(|cmd| cmd.name == context.parameter.name)
- .unwrap()
- .kind;
-
- let mut items = Vec::new();
- for document in request.related_documents() {
- if let SyntaxTree::Latex(tree) = &document.tree {
- for entry in &tree.glossary.entries {
- match (cmd_kind, entry.kind) {
- (Acronym, Acronym) | (General, General) | (General, Acronym) => {
- let label = entry.label().text().to_owned();
- let text_edit = TextEdit::new(context.range, label.clone());
- let item = factory::glossary_entry(request, label, text_edit);
- items.push(item);
- }
- (Acronym, General) => {}
- }
+ .find(|cmd| &cmd.name[1..] == ctx.parameter.name)
+ .unwrap()
+ .kind;
+
+ for doc in req.related() {
+ if let DocumentContent::Latex(table) = &doc.content {
+ for entry in &table.glossary_entries {
+ match (cmd_kind, entry.kind) {
+ (Acronym, Acronym) | (General, General) | (General, Acronym) => {
+ let name = entry.label(&table).text();
+ let item = Item::new(ctx.range, ItemData::GlossaryEntry { name });
+ items.push(item);
}
+ (Acronym, General) => {}
}
}
- items
}
- })
- .await
+ }
+ })
+ .await
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ feature::FeatureTester,
+ protocol::{Range, RangeExt},
+ };
+ use indoc::indoc;
+
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_glossary_entries(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_glossary_entries(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn acronym() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \newacronym{lvm}{LVM}{Logical Volume Manager}
+ \acrfull{foo}
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 9)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_glossary_entries(&req, &mut actual_items).await;
+
+ assert_eq!(actual_items.len(), 1);
+ assert_eq!(actual_items[0].data.label(), "lvm");
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 9, 1, 12));
}
}
diff --git a/support/texlab/src/completion/latex/import.rs b/support/texlab/src/completion/latex/import.rs
index 7f333f17ae..06c28e189b 100644
--- a/support/texlab/src/completion/latex/import.rs
+++ b/support/texlab/src/completion/latex/import.rs
@@ -1,44 +1,40 @@
use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::completion::DATABASE;
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, TextEdit};
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexClassImportProvider;
-
-impl FeatureProvider for LatexClassImportProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- import(request, LatexIncludeKind::Class, factory::class).await
- }
-}
+use crate::{
+ completion::types::{Item, ItemData},
+ components::COMPONENT_DATABASE,
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+ syntax::{LatexIncludeKind, LANGUAGE_DATA},
+};
+use std::{borrow::Cow, collections::HashSet};
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexPackageImportProvider;
-
-impl FeatureProvider for LatexPackageImportProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
+pub async fn complete_latex_classes<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ complete_latex_imports(req, items, LatexIncludeKind::Class, |name| {
+ ItemData::Class { name }
+ })
+ .await;
+}
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- import(request, LatexIncludeKind::Package, factory::package).await
- }
+pub async fn complete_latex_packages<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ complete_latex_imports(req, items, LatexIncludeKind::Package, |name| {
+ ItemData::Package { name }
+ })
+ .await;
}
-async fn import<F>(
- request: &FeatureRequest<CompletionParams>,
+async fn complete_latex_imports<'a, F>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
kind: LatexIncludeKind,
- factory: F,
-) -> Vec<CompletionItem>
-where
- F: Fn(&FeatureRequest<CompletionParams>, &'static str, TextEdit) -> CompletionItem,
+ mut factory: F,
+) where
+ F: FnMut(Cow<'a, str>) -> ItemData<'a>,
{
let extension = if kind == LatexIncludeKind::Package {
"sty"
@@ -50,61 +46,143 @@ where
.include_commands
.iter()
.filter(|cmd| cmd.kind == kind)
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
-
- combinators::argument(request, parameters, |context| {
- async move {
- let mut items = Vec::new();
- for component in &DATABASE.components {
- for file_name in &component.file_names {
- if file_name.ends_with(extension) {
- let stem = &file_name[0..file_name.len() - 4];
- let text_edit = TextEdit::new(context.range, stem.into());
- let item = factory(request, stem, text_edit);
- items.push(item);
- }
- }
- }
- items
- }
+ .map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
+
+ combinators::argument(req, parameters, |ctx| async move {
+ let resolver = req.distro.resolver().await;
+ let mut file_names = HashSet::new();
+ COMPONENT_DATABASE
+ .components
+ .iter()
+ .flat_map(|comp| comp.file_names.iter())
+ .filter(|file_name| file_name.ends_with(extension))
+ .for_each(|file_name| {
+ file_names.insert(file_name);
+ let stem = &file_name[0..file_name.len() - 4];
+ let data = factory(stem.into());
+ let item = Item::new(ctx.range, data);
+ items.push(item);
+ });
+
+ resolver
+ .files_by_name
+ .keys()
+ .filter(|file_name| file_name.ends_with(extension) && !file_names.contains(file_name))
+ .for_each(|file_name| {
+ let stem = &file_name[0..file_name.len() - 4];
+ let data = factory(stem.to_owned().into());
+ let item = Item::new(ctx.range, data);
+ items.push(item);
+ });
})
- .await
+ .await;
}
#[cfg(test)]
mod tests {
use super::*;
- use lsp_types::Position;
-
- #[test]
- fn test_class() {
- let items = test_feature(
- LatexClassImportProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\documentclass{}")],
- main_file: "foo.tex",
- position: Position::new(0, 15),
- ..FeatureSpec::default()
- },
- );
-
- assert!(items.iter().any(|item| item.label == "beamer"));
- assert!(items.iter().all(|item| item.label != "amsmath"));
+ use crate::feature::FeatureTester;
+
+ #[tokio::test]
+ async fn empty_latex_document_class() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_classes(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document_class() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_classes(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_latex_document_package() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_packages(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_package() {
- let items = test_feature(
- LatexPackageImportProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\usepackage{}")],
- main_file: "foo.tex",
- position: Position::new(0, 12),
- ..FeatureSpec::default()
- },
- );
-
- assert!(items.iter().all(|item| item.label != "beamer"));
- assert!(items.iter().any(|item| item.label == "amsmath"));
+ #[tokio::test]
+ async fn empty_bibtex_document_package() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_packages(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn class() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\documentclass{}"#)
+ .main("main.tex")
+ .position(0, 15)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_classes(&req, &mut actual_items).await;
+
+ assert!(actual_items
+ .iter()
+ .any(|item| item.data.label() == "beamer"));
+ assert!(actual_items
+ .iter()
+ .all(|item| item.data.label() != "amsmath"));
+ }
+
+ #[tokio::test]
+ async fn package() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\usepackage{}"#)
+ .main("main.tex")
+ .position(0, 12)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_packages(&req, &mut actual_items).await;
+
+ assert!(actual_items
+ .iter()
+ .all(|item| item.data.label() != "beamer"));
+ assert!(actual_items
+ .iter()
+ .any(|item| item.data.label() == "amsmath"));
}
}
diff --git a/support/texlab/src/completion/latex/include.rs b/support/texlab/src/completion/latex/include.rs
index 2f4090b3f3..1a8555d07c 100644
--- a/support/texlab/src/completion/latex/include.rs
+++ b/support/texlab/src/completion/latex/include.rs
@@ -1,129 +1,133 @@
use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::range::RangeExt;
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, Range, TextEdit};
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::{CompletionParams, Range, RangeExt},
+ syntax::{latex, AstNodeIndex, SyntaxNode, LANGUAGE_DATA},
+};
use std::path::{Path, PathBuf};
-use walkdir::WalkDir;
+use tokio::fs;
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexIncludeCompletionProvider;
+pub async fn complete_latex_includes<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let parameters = LANGUAGE_DATA.include_commands.iter().map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
-impl FeatureProvider for LatexIncludeCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
+ combinators::argument_word(req, parameters, |cmd_node, index| async move {
+ if !req.current().is_file() {
+ return;
+ }
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameters = LANGUAGE_DATA
- .include_commands
- .iter()
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
+ make_items(req, items, cmd_node, index).await;
+ })
+ .await;
+}
- combinators::argument_word(request, parameters, |command, index| {
- async move {
- if !request.document().is_file() {
- return Vec::new();
- }
+async fn make_items<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+ cmd_node: AstNodeIndex,
+ index: usize,
+) -> Option<()> {
+ let table = req.current().content.as_latex()?;
+ let pos = req.params.text_document_position.position;
+ let path_word = table.extract_word(cmd_node, latex::GroupKind::Group, index);
+ let name_range = match path_word {
+ Some(path_word) => Range::new_simple(
+ path_word.start().line,
+ path_word.end().character - path_word.text().split('/').last()?.chars().count() as u64,
+ path_word.end().line,
+ path_word.end().character,
+ ),
+ None => Range::new(pos, pos),
+ };
- let position = request.params.text_document_position.position;
- let mut items = Vec::new();
- let path_word = command.extract_word(index);
- let name_range = match path_word {
- Some(path_word) => Range::new_simple(
- path_word.start().line,
- path_word.end().character
- - path_word.text().split('/').last().unwrap().chars().count() as u64,
- path_word.end().line,
- path_word.end().character,
- ),
- None => Range::new(position, position),
- };
- let directory = current_directory(&request, &command);
+ let cmd = table.as_command(cmd_node)?;
+ let current_dir = current_dir(req, table, cmd_node)?;
+ let mut entries = fs::read_dir(current_dir).await.ok()?;
+ while let Some(entry) = entries.next_entry().await.ok()? {
+ let mut path = entry.path();
- for entry in WalkDir::new(directory)
- .min_depth(1)
- .max_depth(1)
- .follow_links(false)
- .into_iter()
- .filter_map(std::result::Result::ok)
- {
- if entry.file_type().is_file() && is_included(&command, &entry.path()) {
- let mut path = entry.into_path();
- let include_extension = LANGUAGE_DATA
- .include_commands
- .iter()
- .find(|cmd| command.name.text() == cmd.name)
- .unwrap()
- .include_extension;
+ let file_type = entry.file_type().await.ok()?;
+ if file_type.is_file() && is_included(&cmd, &path) {
+ let include_extension = LANGUAGE_DATA
+ .include_commands
+ .iter()
+ .find(|c| cmd.name.text() == c.name)?
+ .include_extension;
- if !include_extension {
- remove_extension(&mut path);
- }
- let text_edit = make_text_edit(name_range, &path);
- items.push(factory::file(request, &path, text_edit));
- } else if entry.file_type().is_dir() {
- let path = entry.into_path();
- let text_edit = make_text_edit(name_range, &path);
- items.push(factory::folder(request, &path, text_edit));
- }
- }
- items
+ if !include_extension {
+ remove_extension(&mut path);
}
- })
- .await
+ let name = path.file_name().unwrap().to_string_lossy().into_owned();
+ let item = Item::new(name_range, ItemData::File { name });
+ items.push(item);
+ } else if file_type.is_dir() {
+ let name = path.file_name().unwrap().to_string_lossy().into_owned();
+ let item = Item::new(name_range, ItemData::Directory { name });
+ items.push(item);
+ }
}
+ Some(())
}
-fn current_directory(
- request: &FeatureRequest<CompletionParams>,
- command: &LatexCommand,
-) -> PathBuf {
- let mut path = request.document().uri.to_file_path().unwrap();
- path = PathBuf::from(path.to_string_lossy().into_owned().replace('\\', "/"));
+fn current_dir(
+ req: &FeatureRequest<CompletionParams>,
+ table: &latex::SymbolTable,
+ cmd_node: AstNodeIndex,
+) -> Option<PathBuf> {
+ let mut path = req
+ .options
+ .latex
+ .as_ref()
+ .and_then(|latex| latex.root_directory.as_ref())
+ .map_or_else(
+ || {
+ let mut path = req.current().uri.to_file_path().unwrap();
+ path.pop();
+ path
+ },
+ Clone::clone,
+ );
- path.pop();
- if let Some(include) = command.extract_word(0) {
+ path = PathBuf::from(path.to_str()?.replace('\\', "/"));
+ if let Some(include) = table.extract_word(cmd_node, latex::GroupKind::Group, 0) {
path.push(include.text());
if !include.text().ends_with('/') {
path.pop();
}
}
- path
+ Some(path)
}
-fn is_included(command: &LatexCommand, file: &Path) -> bool {
+fn is_included(cmd: &latex::Command, file: &Path) -> bool {
if let Some(allowed_extensions) = LANGUAGE_DATA
.include_commands
.iter()
- .find(|cmd| command.name.text() == cmd.name)
- .unwrap()
- .kind
- .extensions()
+ .find(|c| c.name == cmd.name.text())
+ .and_then(|c| c.kind.extensions())
{
file.extension()
- .map(|extension| extension.to_string_lossy().to_lowercase())
- .map(|extension| allowed_extensions.contains(&extension.as_str()))
- .unwrap_or(false)
+ .and_then(|ext| ext.to_str())
+ .map(|ext| ext.to_lowercase())
+ .map(|ext| allowed_extensions.contains(&ext.as_str()))
+ .unwrap_or_default()
} else {
true
}
}
fn remove_extension(path: &mut PathBuf) {
- let stem = path
+ if let Some(stem) = path
.file_stem()
- .map(|stem| stem.to_string_lossy().into_owned());
-
- if let Some(stem) = stem {
+ .and_then(|stem| stem.to_str())
+ .map(ToOwned::to_owned)
+ {
path.pop();
- path.push(PathBuf::from(stem));
+ path.push(stem);
}
}
-
-fn make_text_edit(range: Range, path: &Path) -> TextEdit {
- let text = path.file_name().unwrap().to_string_lossy().into_owned();
- TextEdit::new(range, text)
-}
diff --git a/support/texlab/src/completion/latex/label.rs b/support/texlab/src/completion/latex/label.rs
index f8d74f4a22..91aa80e532 100644
--- a/support/texlab/src/completion/latex/label.rs
+++ b/support/texlab/src/completion/latex/label.rs
@@ -1,156 +1,230 @@
use super::combinators::{self, ArgumentContext, Parameter};
-use crate::completion::factory;
-use crate::range::RangeExt;
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::*;
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::{DocumentView, FeatureRequest},
+ outline::{Outline, OutlineContext, OutlineContextItem},
+ protocol::{CompletionParams, RangeExt},
+ syntax::{
+ latex, LatexLabelKind, LatexLabelReferenceSource, Structure, SyntaxNode, LANGUAGE_DATA,
+ },
+ workspace::DocumentContent,
+};
use std::sync::Arc;
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexLabelCompletionProvider;
+pub async fn complete_latex_labels<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let parameters = LANGUAGE_DATA
+ .label_commands
+ .iter()
+ .filter(|cmd| cmd.kind.is_reference())
+ .map(|cmd| Parameter {
+ name: &cmd.name[1..],
+ index: cmd.index,
+ });
-impl FeatureProvider for LatexLabelCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
+ combinators::argument(req, parameters, |ctx| async move {
+ let source = find_source(ctx);
+ for doc in req.related() {
+ let snapshot = Arc::clone(&req.view.snapshot);
+ let view =
+ DocumentView::analyze(snapshot, Arc::clone(&doc), &req.options, &req.current_dir);
+ let outline = Outline::analyze(&view, &req.options, &req.current_dir);
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameters = LANGUAGE_DATA
- .label_commands
- .iter()
- .filter(|cmd| cmd.kind.is_reference())
- .map(|cmd| Parameter::new(&cmd.name, cmd.index));
-
- combinators::argument(request, parameters, |context| {
- async move {
- let source = Self::find_source(&context);
- let mut items = Vec::new();
- for document in request.related_documents() {
- let workspace = Arc::clone(&request.view.workspace);
- let view = DocumentView::new(workspace, Arc::clone(&document));
- let outline = Outline::from(&view);
-
- if let SyntaxTree::Latex(tree) = &document.tree {
- for label in tree
- .structure
- .labels
- .iter()
- .filter(|label| label.kind == LatexLabelKind::Definition)
- .filter(|label| Self::is_included(tree, label, source))
- {
- let outline_context = OutlineContext::parse(&view, &label, &outline);
- for name in label.names() {
- let text = name.text().to_owned();
- let text_edit = TextEdit::new(context.range, text.clone());
- let item = factory::label(
- request,
- text,
- text_edit,
- outline_context.as_ref(),
- );
- items.push(item);
- }
- }
+ if let DocumentContent::Latex(table) = &doc.content {
+ for label in table
+ .labels
+ .iter()
+ .filter(|label| label.kind == LatexLabelKind::Definition)
+ .filter(|label| is_included(&table, label, source))
+ {
+ let outline_ctx = OutlineContext::parse(&view, &outline, *label);
+
+ let kind = match outline_ctx.as_ref().map(|ctx| &ctx.item) {
+ Some(OutlineContextItem::Section { .. }) => Structure::Section,
+ Some(OutlineContextItem::Caption { .. }) => Structure::Float,
+ Some(OutlineContextItem::Theorem { .. }) => Structure::Theorem,
+ Some(OutlineContextItem::Equation) => Structure::Equation,
+ Some(OutlineContextItem::Item) => Structure::Item,
+ None => Structure::Label,
+ };
+
+ for name in label.names(&table) {
+ let header = outline_ctx.as_ref().and_then(|ctx| ctx.detail());
+ let footer = outline_ctx.as_ref().and_then(|ctx| match &ctx.item {
+ OutlineContextItem::Caption { text, .. } => Some(text.clone()),
+ _ => None,
+ });
+
+ let text = outline_ctx
+ .as_ref()
+ .map(|ctx| format!("{} {}", name.text(), ctx.reference()))
+ .unwrap_or_else(|| name.text().into());
+
+ let item = Item::new(
+ ctx.range,
+ ItemData::Label {
+ name: name.text(),
+ kind,
+ header,
+ footer,
+ text,
+ },
+ );
+ items.push(item);
}
}
- items
}
- })
- .await
- }
+ }
+ })
+ .await;
}
-impl LatexLabelCompletionProvider {
- fn find_source(context: &ArgumentContext) -> LatexLabelReferenceSource {
- match LANGUAGE_DATA
- .label_commands
- .iter()
- .find(|cmd| cmd.name == context.parameter.name && cmd.index == context.parameter.index)
- .map(|cmd| cmd.kind)
- .unwrap()
- {
- LatexLabelKind::Definition => unreachable!(),
- LatexLabelKind::Reference(source) => source,
- }
+fn find_source(ctx: ArgumentContext) -> LatexLabelReferenceSource {
+ match LANGUAGE_DATA
+ .label_commands
+ .iter()
+ .find(|cmd| &cmd.name[1..] == ctx.parameter.name && cmd.index == ctx.parameter.index)
+ .map(|cmd| cmd.kind)
+ .unwrap()
+ {
+ LatexLabelKind::Definition => unreachable!(),
+ LatexLabelKind::Reference(source) => source,
}
+}
- fn is_included(
- tree: &LatexSyntaxTree,
- label: &LatexLabel,
- source: LatexLabelReferenceSource,
- ) -> bool {
- match source {
- LatexLabelReferenceSource::Everything => true,
- LatexLabelReferenceSource::Math => tree
- .env
- .environments
- .iter()
- .filter(|env| env.left.is_math())
- .any(|env| env.range().contains_exclusive(label.start())),
- }
+fn is_included(
+ table: &latex::SymbolTable,
+ label: &latex::Label,
+ source: LatexLabelReferenceSource,
+) -> bool {
+ let label_range = table[label.parent].range();
+ match source {
+ LatexLabelReferenceSource::Everything => true,
+ LatexLabelReferenceSource::Math => table
+ .environments
+ .iter()
+ .filter(|env| env.left.is_math(&table))
+ .any(|env| env.range(&table).contains_exclusive(label_range.start)),
}
}
#[cfg(test)]
mod tests {
use super::*;
- use lsp_types::Position;
-
- #[test]
- fn test_inside_of_ref() {
- let items = test_feature(
- LatexLabelCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file(
- "foo.tex",
- "\\addbibresource{bar.bib}\\include{baz}\n\\ref{}",
- ),
- FeatureSpec::file("bar.bib", ""),
- FeatureSpec::file("baz.tex", "\\label{foo}\\label{bar}\\ref{baz}"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 5),
- ..FeatureSpec::default()
- },
- );
- let labels: Vec<&str> = items.iter().map(|item| item.label.as_ref()).collect();
- assert_eq!(labels, vec!["foo", "bar"]);
+ use crate::feature::FeatureTester;
+ use indoc::indoc;
+
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_labels(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_labels(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn inside_of_ref() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \addbibresource{bar.bib}
+ \include{baz}
+ \ref{}
+ "#
+ ),
+ )
+ .file("bar.bib", "")
+ .file("baz.tex", r#"\label{foo}\label{bar}\ref{baz}"#)
+ .main("foo.tex")
+ .position(2, 5)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_labels(&req, &mut actual_items).await;
+
+ let actual_labels: Vec<_> = actual_items
+ .into_iter()
+ .map(|item| item.data.label().to_owned())
+ .collect();
+ assert_eq!(actual_labels, vec!["foo", "bar"]);
}
- #[test]
- fn test_outside_of_ref() {
- let items = test_feature(
- LatexLabelCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file("foo.tex", "\\include{bar}\\ref{}"),
- FeatureSpec::file("bar.tex", "\\label{foo}\\label{bar}"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 6),
- ..FeatureSpec::default()
- },
- );
- assert!(items.is_empty());
+ #[tokio::test]
+ async fn outside_of_ref() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \include{bar}
+ \ref{}
+ "#
+ ),
+ )
+ .file("bar.tex", r#"\label{foo}\label{bar}"#)
+ .main("foo.tex")
+ .position(1, 6)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_labels(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_eqref() {
- let items = test_feature(
- LatexLabelCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file(
- "foo.tex",
- "\\begin{align}\\label{foo}\\end{align}\\label{bar}\n\\eqref{}",
- )],
- main_file: "foo.tex",
- position: Position::new(1, 7),
- ..FeatureSpec::default()
- },
- );
- let labels: Vec<&str> = items.iter().map(|item| item.label.as_ref()).collect();
- assert_eq!(labels, vec!["foo"]);
+ #[tokio::test]
+ async fn eqref() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \begin{align}\label{foo}\end{align}\label{bar}
+ \eqref{}
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 7)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_labels(&req, &mut actual_items).await;
+
+ let actual_labels: Vec<_> = actual_items
+ .into_iter()
+ .map(|item| item.data.label().to_owned())
+ .collect();
+
+ assert_eq!(actual_labels, vec!["foo"]);
}
}
diff --git a/support/texlab/src/completion/latex/mod.rs b/support/texlab/src/completion/latex/mod.rs
index 2902678c11..519d00e5f7 100644
--- a/support/texlab/src/completion/latex/mod.rs
+++ b/support/texlab/src/completion/latex/mod.rs
@@ -1,14 +1,14 @@
pub mod argument;
-pub mod begin_command;
+pub mod begin_cmd;
pub mod citation;
pub mod color;
pub mod color_model;
-pub mod combinators;
+mod combinators;
pub mod component;
pub mod glossary;
pub mod import;
pub mod include;
pub mod label;
pub mod theorem;
-pub mod tikz;
+pub mod tikz_lib;
pub mod user;
diff --git a/support/texlab/src/completion/latex/theorem.rs b/support/texlab/src/completion/latex/theorem.rs
index 993ef5c7e8..a8706bed0b 100644
--- a/support/texlab/src/completion/latex/theorem.rs
+++ b/support/texlab/src/completion/latex/theorem.rs
@@ -1,70 +1,115 @@
use super::combinators;
-use crate::completion::factory::{self, LatexComponentId};
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, TextEdit};
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+};
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexTheoremEnvironmentCompletionProvider;
-
-impl FeatureProvider for LatexTheoremEnvironmentCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- combinators::environment(request, |context| {
- async move {
- let mut items = Vec::new();
- for document in request.related_documents() {
- if let SyntaxTree::Latex(tree) = &document.tree {
- for theorem in &tree.math.theorem_definitions {
- let name = theorem.name().text().to_owned();
- let text_edit = TextEdit::new(context.range, name.clone());
- let item = factory::environment(
- request,
- name,
- text_edit,
- &LatexComponentId::User,
- );
- items.push(item);
- }
- }
- }
- items
+pub async fn complete_latex_theorem_environments<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ combinators::environment(req, |ctx| async move {
+ for table in req
+ .related()
+ .into_iter()
+ .filter_map(|doc| doc.content.as_latex())
+ {
+ for theorem in &table.theorem_definitions {
+ let name = theorem.name(&table).text();
+ let data = ItemData::UserEnvironment { name };
+ let item = Item::new(ctx.range, data);
+ items.push(item);
}
- })
- .await
- }
+ }
+ })
+ .await;
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::range::RangeExt;
- use lsp_types::{Position, Range};
- use std::borrow::Cow;
+ use crate::{
+ feature::FeatureTester,
+ protocol::{Range, RangeExt},
+ };
+ use indoc::indoc;
+
+ #[tokio::test]
+ async fn empty_latex_document() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_theorem_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_theorem_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn inside_begin() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \newtheorem{theorem}{Theorem}
+ \begin{th}
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 8)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_theorem_environments(&req, &mut actual_items).await;
+
+ assert_eq!(actual_items.len(), 1);
+ assert_eq!(actual_items[0].data.label(), "theorem");
+ assert_eq!(actual_items[0].range, Range::new_simple(1, 7, 1, 9));
+ }
+
+ #[tokio::test]
+ async fn outside_begin() {
+ let req = FeatureTester::new()
+ .file(
+ "main.tex",
+ indoc!(
+ r#"
+ \newtheorem{theorem}{Theorem}
+ \begin{th}
+ "#
+ ),
+ )
+ .main("main.tex")
+ .position(1, 10)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_theorem_environments(&req, &mut actual_items).await;
- #[test]
- fn test() {
- let items = test_feature(
- LatexTheoremEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file(
- "foo.tex",
- "\\newtheorem{theorem}{Theorem}\n\\begin{th}",
- )],
- main_file: "foo.tex",
- position: Position::new(1, 8),
- ..FeatureSpec::default()
- },
- );
- assert_eq!(items.len(), 1);
- assert_eq!(items[0].label, Cow::from("theorem"));
- assert_eq!(
- items[0].text_edit.as_ref().map(|edit| edit.range),
- Some(Range::new_simple(1, 7, 1, 9))
- );
+ assert!(actual_items.is_empty());
}
}
diff --git a/support/texlab/src/completion/latex/tikz.rs b/support/texlab/src/completion/latex/tikz.rs
deleted file mode 100644
index 2d6740e1a8..0000000000
--- a/support/texlab/src/completion/latex/tikz.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-use super::combinators::{self, Parameter};
-use crate::completion::factory;
-use crate::syntax::LANGUAGE_DATA;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use lsp_types::{CompletionItem, CompletionParams, TextEdit};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexPgfLibraryCompletionProvider;
-
-impl FeatureProvider for LatexPgfLibraryCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameter = Parameter::new("\\usepgflibrary", 0);
- combinators::argument(request, std::iter::once(parameter), |context| {
- async move {
- let mut items = Vec::new();
- for name in &LANGUAGE_DATA.pgf_libraries {
- let text_edit = TextEdit::new(context.range, name.into());
- let item = factory::pgf_library(request, name, text_edit);
- items.push(item);
- }
- items
- }
- })
- .await
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexTikzLibraryCompletionProvider;
-
-impl FeatureProvider for LatexTikzLibraryCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- let parameter = Parameter::new("\\usetikzlibrary", 0);
- combinators::argument(request, std::iter::once(parameter), |context| {
- async move {
- let mut items = Vec::new();
- for name in &LANGUAGE_DATA.tikz_libraries {
- let text_edit = TextEdit::new(context.range, name.into());
- let item = factory::tikz_library(request, name, text_edit);
- items.push(item);
- }
- items
- }
- })
- .await
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use lsp_types::Position;
-
- #[test]
- fn test_pgf_library() {
- let items = test_feature(
- LatexPgfLibraryCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\usepgflibrary{}")],
- main_file: "foo.tex",
- position: Position::new(0, 15),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- }
-
- #[test]
- fn test_tikz_library() {
- let items = test_feature(
- LatexTikzLibraryCompletionProvider,
- FeatureSpec {
- files: vec![FeatureSpec::file("foo.tex", "\\usetikzlibrary{}")],
- main_file: "foo.tex",
- position: Position::new(0, 16),
- ..FeatureSpec::default()
- },
- );
- assert!(!items.is_empty());
- }
-}
diff --git a/support/texlab/src/completion/latex/tikz_lib.rs b/support/texlab/src/completion/latex/tikz_lib.rs
new file mode 100644
index 0000000000..fb51fd7229
--- /dev/null
+++ b/support/texlab/src/completion/latex/tikz_lib.rs
@@ -0,0 +1,138 @@
+use super::combinators::{self, Parameter};
+use crate::{
+ completion::types::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::CompletionParams,
+ syntax::LANGUAGE_DATA,
+};
+use std::iter;
+
+pub async fn complete_latex_pgf_libraries<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let param = Parameter {
+ name: "usepgflibrary",
+ index: 0,
+ };
+ combinators::argument(req, iter::once(param), |ctx| async move {
+ for name in &LANGUAGE_DATA.pgf_libraries {
+ let item = Item::new(ctx.range, ItemData::PgfLibrary { name });
+ items.push(item);
+ }
+ })
+ .await;
+}
+
+pub async fn complete_latex_tikz_libraries<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ let param = Parameter {
+ name: "usetikzlibrary",
+ index: 0,
+ };
+ combinators::argument(req, iter::once(param), |ctx| async move {
+ for name in &LANGUAGE_DATA.tikz_libraries {
+ let item = Item::new(ctx.range, ItemData::TikzLibrary { name });
+ items.push(item);
+ }
+ })
+ .await;
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::feature::FeatureTester;
+
+ #[tokio::test]
+ async fn empty_latex_document_pgf() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_pgf_libraries(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document_pgf() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_pgf_libraries(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_latex_document_tikz() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_tikz_libraries(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document_tikz() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_tikz_libraries(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn pgf_library() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\usepgflibrary{}"#)
+ .main("main.tex")
+ .position(0, 15)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_pgf_libraries(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn tikz_library() {
+ let req = FeatureTester::new()
+ .file("main.tex", r#"\usetikzlibrary{}"#)
+ .main("main.tex")
+ .position(0, 16)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_tikz_libraries(&req, &mut actual_items).await;
+
+ assert!(!actual_items.is_empty());
+ }
+}
diff --git a/support/texlab/src/completion/latex/user.rs b/support/texlab/src/completion/latex/user.rs
index 890a75882b..0a5706958a 100644
--- a/support/texlab/src/completion/latex/user.rs
+++ b/support/texlab/src/completion/latex/user.rs
@@ -1,156 +1,204 @@
use super::combinators;
-use crate::completion::factory::{self, LatexComponentId};
-use crate::syntax::*;
-use crate::workspace::*;
-use futures_boxed::boxed;
-use itertools::Itertools;
-use lsp_types::*;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexUserCommandCompletionProvider;
-
-impl FeatureProvider for LatexUserCommandCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- combinators::command(request, |current_command| {
- async move {
- let mut items = Vec::new();
- for document in request.related_documents() {
- if let SyntaxTree::Latex(tree) = &document.tree {
- tree.commands
- .iter()
- .filter(|command| command.range() != current_command.range())
- .map(|command| &command.name.text()[1..])
- .unique()
- .map(|command| {
- let text_edit = TextEdit::new(
- current_command.short_name_range(),
- command.to_owned(),
- );
- factory::command(
- request,
- command.to_owned(),
- None,
- None,
- text_edit,
- &LatexComponentId::User,
- )
- })
- .for_each(|item| items.push(item));
- }
- }
- items
- }
- })
- .await
- }
-}
+use crate::{
+ completion::{Item, ItemData},
+ feature::FeatureRequest,
+ protocol::{CompletionParams, Range},
+ syntax::latex,
+ workspace::DocumentContent,
+};
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct LatexUserEnvironmentCompletionProvider;
-
-impl FeatureProvider for LatexUserEnvironmentCompletionProvider {
- type Params = CompletionParams;
- type Output = Vec<CompletionItem>;
-
- #[boxed]
- async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
- combinators::environment(request, |context| {
- async move {
- let mut items = Vec::new();
- for document in request.related_documents() {
- if let SyntaxTree::Latex(tree) = &document.tree {
- for environment in &tree.env.environments {
- if environment.left.command == context.command
- || environment.right.command == context.command
- {
- continue;
- }
-
- if let Some(item) =
- Self::make_item(request, &environment.left, context.range)
- {
- items.push(item);
- }
-
- if let Some(item) =
- Self::make_item(request, &environment.right, context.range)
- {
- items.push(item);
- }
- }
- }
- }
- items
- }
- })
- .await
- }
+pub async fn complete_latex_user_commands<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
+ combinators::command(req, |current_cmd_node| async move {
+ let current_cmd = req
+ .current()
+ .content
+ .as_latex()
+ .unwrap()
+ .as_command(current_cmd_node)
+ .unwrap();
+
+ for table in req
+ .related()
+ .into_iter()
+ .flat_map(|doc| doc.content.as_latex())
+ {
+ table
+ .commands
+ .iter()
+ .filter(|cmd_node| **cmd_node != current_cmd_node)
+ .map(|cmd_node| {
+ let name = &table.as_command(*cmd_node).unwrap().name.text()[1..];
+ Item::new(
+ current_cmd.short_name_range(),
+ ItemData::UserCommand { name },
+ )
+ })
+ .for_each(|item| items.push(item));
+ }
+ })
+ .await;
}
-impl LatexUserEnvironmentCompletionProvider {
+pub async fn complete_latex_user_environments<'a>(
+ req: &'a FeatureRequest<CompletionParams>,
+ items: &mut Vec<Item<'a>>,
+) {
fn make_item(
- request: &FeatureRequest<CompletionParams>,
- delimiter: &LatexEnvironmentDelimiter,
+ table: &latex::SymbolTable,
+ delim: latex::EnvironmentDelimiter,
name_range: Range,
- ) -> Option<CompletionItem> {
- if let Some(name) = delimiter.name() {
- let text = name.text().to_owned();
- let text_edit = TextEdit::new(name_range, text.clone());
- let item = factory::environment(request, text, text_edit, &LatexComponentId::User);
- return Some(item);
- }
- None
+ ) -> Option<Item> {
+ delim
+ .name(&table)
+ .map(|name| Item::new(name_range, ItemData::UserEnvironment { name: &name.text() }))
}
+
+ combinators::environment(req, |ctx| async move {
+ for doc in req.related() {
+ if let DocumentContent::Latex(table) = &doc.content {
+ for env in &table.environments {
+ if (env.left.parent == ctx.node || env.right.parent == ctx.node)
+ && doc.uri == req.current().uri
+ {
+ continue;
+ }
+
+ if let Some(item) = make_item(&table, env.left, ctx.range) {
+ items.push(item);
+ }
+
+ if let Some(item) = make_item(&table, env.right, ctx.range) {
+ items.push(item);
+ }
+ }
+ }
+ }
+ })
+ .await;
}
#[cfg(test)]
mod tests {
use super::*;
- use lsp_types::Position;
-
- #[test]
- fn test_command() {
- let items = test_feature(
- LatexUserCommandCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file("foo.tex", "\\include{bar.tex}\n\\foo"),
- FeatureSpec::file("bar.tex", "\\bar"),
- FeatureSpec::file("baz.tex", "\\baz"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 2),
- ..FeatureSpec::default()
- },
- );
- let labels: Vec<&str> = items.iter().map(|item| item.label.as_ref()).collect();
- assert_eq!(labels, vec!["include", "bar"]);
+ use crate::feature::FeatureTester;
+ use indoc::indoc;
+ use itertools::Itertools;
+
+ #[tokio::test]
+ async fn empty_latex_document_command() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_user_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document_command() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_user_commands(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
}
- #[test]
- fn test_environment() {
- let items = test_feature(
- LatexUserEnvironmentCompletionProvider,
- FeatureSpec {
- files: vec![
- FeatureSpec::file("foo.tex", "\\include{bar.tex}\n\\begin{foo}"),
- FeatureSpec::file("bar.tex", "\\begin{bar}\\end{bar}"),
- FeatureSpec::file("baz.tex", "\\begin{baz}\\end{baz}"),
- ],
- main_file: "foo.tex",
- position: Position::new(1, 9),
- ..FeatureSpec::default()
- },
- );
- let labels: Vec<&str> = items
- .iter()
- .map(|item| item.label.as_ref())
+ #[tokio::test]
+ async fn empty_latex_document_environment() {
+ let req = FeatureTester::new()
+ .file("main.tex", "")
+ .main("main.tex")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_user_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn empty_bibtex_document_environment() {
+ let req = FeatureTester::new()
+ .file("main.bib", "")
+ .main("main.bib")
+ .position(0, 0)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+ complete_latex_user_environments(&req, &mut actual_items).await;
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[tokio::test]
+ async fn command() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \include{bar}
+ \foo
+ "#
+ ),
+ )
+ .file("bar.tex", r#"\bar"#)
+ .file("baz.tex", r#"\baz"#)
+ .main("foo.tex")
+ .position(1, 2)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_user_commands(&req, &mut actual_items).await;
+
+ let actual_labels: Vec<_> = actual_items
+ .into_iter()
+ .map(|item| item.data.label().to_owned())
+ .collect();
+ assert_eq!(actual_labels, vec!["include", "bar"]);
+ }
+
+ #[tokio::test]
+ async fn environment() {
+ let req = FeatureTester::new()
+ .file(
+ "foo.tex",
+ indoc!(
+ r#"
+ \include{bar}
+ \begin{foo}
+ "#
+ ),
+ )
+ .file("bar.tex", r#"\begin{bar}\end{bar}"#)
+ .file("baz.tex", r#"\begin{baz}\end{baz}"#)
+ .main("foo.tex")
+ .position(1, 9)
+ .test_completion_request()
+ .await;
+ let mut actual_items = Vec::new();
+
+ complete_latex_user_environments(&req, &mut actual_items).await;
+
+ let actual_labels: Vec<_> = actual_items
+ .into_iter()
+ .map(|item| item.data.label().to_owned())
.unique()
.collect();
- assert_eq!(labels, vec!["bar"]);
+ assert_eq!(actual_labels, vec!["bar"]);
}
}