diff options
Diffstat (limited to 'support/texlab/src/completion/latex/label.rs')
-rw-r--r-- | support/texlab/src/completion/latex/label.rs | 338 |
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"]); } } |