summaryrefslogtreecommitdiff
path: root/support/texlab/src/completion/latex/label.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/completion/latex/label.rs')
-rw-r--r--support/texlab/src/completion/latex/label.rs338
1 files changed, 206 insertions, 132 deletions
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"]);
}
}