diff options
author | Norbert Preining <norbert@preining.info> | 2021-06-04 03:02:48 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2021-06-04 03:02:48 +0000 |
commit | a5ae129f3aa3a3a0aeaa21c55b75a1d543fe4111 (patch) | |
tree | fe5e1e212aec203e668df8581a17374cdde352a1 /support/texlab/src/features/completion | |
parent | d231c6331f53a048d29c2f847254a9f56d5ee2dd (diff) |
CTAN sync 202106040302
Diffstat (limited to 'support/texlab/src/features/completion')
14 files changed, 137 insertions, 181 deletions
diff --git a/support/texlab/src/features/completion/acronym_ref.rs b/support/texlab/src/features/completion/acronym_ref.rs index 9d8bcc6c5f..db3e34f9eb 100644 --- a/support/texlab/src/features/completion/acronym_ref.rs +++ b/support/texlab/src/features/completion/acronym_ref.rs @@ -1,5 +1,4 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; use crate::{ @@ -16,18 +15,9 @@ pub fn complete_acronyms<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let group = latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; - + let (_, range, group) = context.find_curly_group_word()?; latex::AcronymReference::cast(group.syntax().parent()?)?; - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; - for document in &context.request.subset.documents { if let Some(data) = document.data.as_latex() { for name in data @@ -35,8 +25,8 @@ pub fn complete_acronyms<'a>( .descendants() .filter_map(latex::AcronymDefinition::cast) .filter_map(|node| node.name()) - .filter_map(|name| name.word()) - .map(|name| name.text()) + .filter_map(|name| name.key()) + .map(|name| name.to_string()) { items.push(InternalCompletionItem::new( range, diff --git a/support/texlab/src/features/completion/citation.rs b/support/texlab/src/features/completion/citation.rs index 6df3364099..0a7cc26b10 100644 --- a/support/texlab/src/features/completion/citation.rs +++ b/support/texlab/src/features/completion/citation.rs @@ -26,12 +26,14 @@ pub fn complete_citations<'a>( let token = context.cursor.as_latex()?; let range = if token.kind() == latex::WORD { - token.text_range() + latex::Key::cast(token.parent()) + .map(|key| key.small_range()) + .or_else(|| latex::Text::cast(token.parent()).map(|text| text.small_range()))? } else { TextRange::empty(context.offset) }; - check_citation(context, token).or_else(|| check_acronym(token))?; + check_citation(context).or_else(|| check_acronym(context))?; for document in &context.request.subset.documents { if let Some(data) = document.data.as_bibtex() { for entry in data.root.children().filter_map(bibtex::Entry::cast) { @@ -46,20 +48,17 @@ pub fn complete_citations<'a>( Some(()) } -fn check_citation( - context: &CursorContext<CompletionParams>, - token: &latex::SyntaxToken, -) -> Option<()> { - let group = latex::CurlyGroupWordList::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; +fn check_citation(context: &CursorContext<CompletionParams>) -> Option<()> { + let (_, _, group) = context.find_curly_group_word_list()?; latex::Citation::cast(group.syntax().parent()?)?; Some(()) } -fn check_acronym(token: &latex::SyntaxToken) -> Option<()> { +fn check_acronym(context: &CursorContext<CompletionParams>) -> Option<()> { + let token = context.cursor.as_latex()?; + let pair = token.ancestors().find_map(latex::KeyValuePair::cast)?; - let mut key_words = pair.key()?.words(); - if key_words.next()?.text() != "cite" || key_words.next().is_some() { + if pair.key()?.to_string() != "cite" { return None; } @@ -72,7 +71,7 @@ fn make_item<'a>( entry: bibtex::Entry<'a>, range: TextRange, ) -> Option<InternalCompletionItem<'a>> { - let key = entry.key()?.text(); + let key = entry.key()?.to_string(); let ty = LANGUAGE_DATA .find_entry_type(&entry.ty()?.text()[1..]) .map(|ty| Structure::Entry(ty.category)) @@ -98,7 +97,7 @@ fn make_item<'a>( range, InternalCompletionItemData::Citation { uri: Arc::clone(&document.uri), - key: key.into(), + key, text, ty, }, @@ -171,6 +170,29 @@ mod tests { } #[test] + fn test_latex_two_words() { + let request = FeatureTester::builder() + .files(vec![ + ("main.tex", "\\addbibresource{main.bib}\n\\cite{foo}"), + ("main.bib", "@article{foo bar,}"), + ]) + .main("main.tex") + .line(1) + .character(6) + .build() + .completion(); + + let context = CursorContext::new(request); + let mut actual_items = Vec::new(); + complete_citations(&context, &mut actual_items, CancellationToken::none()); + + assert!(!actual_items.is_empty()); + for item in actual_items { + assert_eq!(item.range, TextRange::new(32.into(), 35.into())); + } + } + + #[test] fn test_latex_open_brace() { let request = FeatureTester::builder() .files(vec![ @@ -241,4 +263,30 @@ mod tests { assert_eq!(item.range, TextRange::new(54.into(), 54.into())); } } + + #[test] + fn test_latex_acronym_two_words() { + let request = FeatureTester::builder() + .files(vec![ + ( + "main.tex", + "\\addbibresource{main.bib}\n\\DeclareAcronym{foo}{cite={\na b}}", + ), + ("main.bib", "@article{foo,}"), + ]) + .main("main.tex") + .line(2) + .character(0) + .build() + .completion(); + + let context = CursorContext::new(request); + let mut actual_items = Vec::new(); + complete_citations(&context, &mut actual_items, CancellationToken::none()); + + assert!(!actual_items.is_empty()); + for item in actual_items { + assert_eq!(item.range, TextRange::new(54.into(), 57.into())); + } + } } diff --git a/support/texlab/src/features/completion/color.rs b/support/texlab/src/features/completion/color.rs index 0e94af15fe..15a7c341f5 100644 --- a/support/texlab/src/features/completion/color.rs +++ b/support/texlab/src/features/completion/color.rs @@ -1,5 +1,4 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; use crate::{ @@ -17,15 +16,7 @@ pub fn complete_colors<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; - - let group = latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; + let (_, range, group) = context.find_curly_group_word()?; latex::ColorReference::cast(group.syntax().parent()?)?; for name in &LANGUAGE_DATA.colors { diff --git a/support/texlab/src/features/completion/color_model.rs b/support/texlab/src/features/completion/color_model.rs index f7f09b6367..a24953caa5 100644 --- a/support/texlab/src/features/completion/color_model.rs +++ b/support/texlab/src/features/completion/color_model.rs @@ -18,15 +18,7 @@ pub fn complete_color_models<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; - - check_color_definition(context, token) - .or_else(|| check_color_definition_set(context, token))?; + let range = check_color_definition(context).or_else(|| check_color_definition_set(context))?; for name in MODEL_NAMES { items.push(InternalCompletionItem::new( @@ -38,30 +30,23 @@ pub fn complete_color_models<'a>( Some(()) } -fn check_color_definition( - context: &CursorContext<CompletionParams>, - token: &latex::SyntaxToken, -) -> Option<()> { - let group = latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; +fn check_color_definition(context: &CursorContext<CompletionParams>) -> Option<TextRange> { + let (_, range, group) = context.find_curly_group_word()?; + let definition = latex::ColorDefinition::cast(group.syntax().parent()?)?; definition .model() .filter(|model| model.syntax().text_range() == group.syntax().text_range())?; - Some(()) + Some(range) } -fn check_color_definition_set( - context: &CursorContext<CompletionParams>, - token: &latex::SyntaxToken, -) -> Option<()> { - let group = latex::CurlyGroupWordList::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; +fn check_color_definition_set(context: &CursorContext<CompletionParams>) -> Option<TextRange> { + let (_, range, group) = context.find_curly_group_word_list()?; let definition = latex::ColorSetDefinition::cast(group.syntax().parent()?)?; definition .model_list() .filter(|model| model.syntax().text_range() == group.syntax().text_range())?; - Some(()) + Some(range) } #[cfg(test)] diff --git a/support/texlab/src/features/completion/component_environment.rs b/support/texlab/src/features/completion/component_environment.rs index fca9d03780..d4340a160a 100644 --- a/support/texlab/src/features/completion/component_environment.rs +++ b/support/texlab/src/features/completion/component_environment.rs @@ -1,12 +1,7 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; -use crate::{ - component_db::COMPONENT_DATABASE, - features::cursor::CursorContext, - syntax::{latex, CstNode}, -}; +use crate::{component_db::COMPONENT_DATABASE, features::cursor::CursorContext}; use super::types::{InternalCompletionItem, InternalCompletionItemData}; @@ -17,18 +12,7 @@ pub fn complete_component_environments<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let group = latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; - if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) { - return None; - } - - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; + let (_, range) = context.find_environment_name()?; for component in COMPONENT_DATABASE.linked_components(&context.request.subset) { cancellation_token.result().ok()?; diff --git a/support/texlab/src/features/completion/field.rs b/support/texlab/src/features/completion/field.rs index d7297b481b..ad1042b04d 100644 --- a/support/texlab/src/features/completion/field.rs +++ b/support/texlab/src/features/completion/field.rs @@ -26,7 +26,7 @@ pub fn complete_fields<'a>( let parent = token.parent(); if let Some(entry) = bibtex::Entry::cast(parent) { - if entry.key()?.text_range() == token.text_range() { + if entry.key()?.small_range() == token.text_range() { return None; } } else { diff --git a/support/texlab/src/features/completion/glossary_ref.rs b/support/texlab/src/features/completion/glossary_ref.rs index 4529129fd7..66d20e46ff 100644 --- a/support/texlab/src/features/completion/glossary_ref.rs +++ b/support/texlab/src/features/completion/glossary_ref.rs @@ -1,5 +1,4 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; use crate::{ @@ -16,15 +15,8 @@ pub fn complete_glossary_entries<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let group = latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; + let (_, range, group) = context.find_curly_group_word()?; latex::GlossaryEntryReference::cast(group.syntax().parent()?)?; - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; for document in &context.request.subset.documents { if let Some(data) = document.data.as_latex() { @@ -33,8 +25,8 @@ pub fn complete_glossary_entries<'a>( if let Some(name) = latex::GlossaryEntryDefinition::cast(node) .and_then(|entry| entry.name()) - .and_then(|name| name.word()) - .map(|name| name.text()) + .and_then(|name| name.key()) + .map(|name| name.to_string()) { items.push(InternalCompletionItem::new( range, @@ -42,8 +34,8 @@ pub fn complete_glossary_entries<'a>( )); } else if let Some(name) = latex::AcronymDefinition::cast(node) .and_then(|entry| entry.name()) - .and_then(|name| name.word()) - .map(|name| name.text()) + .and_then(|name| name.key()) + .map(|name| name.to_string()) { items.push(InternalCompletionItem::new( range, diff --git a/support/texlab/src/features/completion/import.rs b/support/texlab/src/features/completion/import.rs index ac5f2e6e40..da4c12c02b 100644 --- a/support/texlab/src/features/completion/import.rs +++ b/support/texlab/src/features/completion/import.rs @@ -1,5 +1,4 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; use rustc_hash::FxHashSet; use smol_str::SmolStr; @@ -17,15 +16,7 @@ pub fn complete_imports<'a>( items: &mut Vec<InternalCompletionItem<'a>>, cancellation_token: &CancellationToken, ) -> Option<()> { - let token = context.cursor.as_latex()?; - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; - - let group = latex::CurlyGroupWordList::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; + let (_, range, group) = context.find_curly_group_word_list()?; let (extension, mut factory): ( &str, @@ -76,6 +67,8 @@ pub fn complete_imports<'a>( #[cfg(test)] mod tests { + use cstree::TextRange; + use crate::features::testing::FeatureTester; use super::*; diff --git a/support/texlab/src/features/completion/include.rs b/support/texlab/src/features/completion/include.rs index c711ea5ce7..4a8e561866 100644 --- a/support/texlab/src/features/completion/include.rs +++ b/support/texlab/src/features/completion/include.rs @@ -25,15 +25,8 @@ pub fn complete_includes<'a>( return None; } - let token = context.cursor.as_latex()?; - let (path_text, path_range) = if token.kind() == latex::WORD { - (token.text(), token.text_range()) - } else { - ("", TextRange::empty(context.offset)) - }; + let (path_text, path_range, group) = context.find_curly_group_word_list()?; - let group = latex::CurlyGroupWordList::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; let include = group.syntax().parent()?; let (include_extension, extensions): (bool, &[&str]) = match include.kind() { latex::PACKAGE_INCLUDE => (false, &["sty"]), @@ -62,7 +55,7 @@ pub fn complete_includes<'a>( TextRange::new(start, path_range.end()) }; - let current_dir = current_dir(context, path_text)?; + let current_dir = current_dir(context, &path_text)?; for entry in fs::read_dir(current_dir).ok()?.filter_map(Result::ok) { let mut path = entry.path(); diff --git a/support/texlab/src/features/completion/label.rs b/support/texlab/src/features/completion/label.rs index 2f7e2d8f77..3efb168d0d 100644 --- a/support/texlab/src/features/completion/label.rs +++ b/support/texlab/src/features/completion/label.rs @@ -18,26 +18,7 @@ pub fn complete_labels<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let is_math = latex::CurlyGroupWordList::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group)) - .and_then(|group| group.syntax().parent()) - .and_then(|reference| latex::LabelReference::cast(reference)) - .and_then(|refernce| refernce.command()) - .map(|reference| reference.text() == "\\eqref") - .or_else(|| { - latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group)) - .and_then(|group| group.syntax().parent()) - .and_then(|reference| latex::LabelReferenceRange::cast(reference)) - .map(|_| false) - })?; - - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; + let (range, is_math) = find_reference(context).or_else(|| find_reference_range(context))?; for document in &context.request.subset.documents { if let Some(data) = document.data.as_latex() { @@ -49,10 +30,10 @@ pub fn complete_labels<'a>( { if let Some(name) = label .name() - .and_then(|name| name.word()) - .map(|name| name.text()) + .and_then(|name| name.key()) + .map(|name| name.to_string()) { - match render_label(&context.request.subset, name, Some(label)) { + match render_label(&context.request.subset, &name, Some(label)) { Some(rendered_label) => { let kind = match &rendered_label.object { LabelledObject::Section { .. } => Structure::Section, @@ -112,6 +93,19 @@ pub fn complete_labels<'a>( Some(()) } +fn find_reference(context: &CursorContext<CompletionParams>) -> Option<(TextRange, bool)> { + let (_, range, group) = context.find_curly_group_word_list()?; + let reference = latex::LabelReference::cast(group.syntax().parent()?)?; + let is_math = reference.command()?.text() == "\\eqref"; + Some((range, is_math)) +} + +fn find_reference_range(context: &CursorContext<CompletionParams>) -> Option<(TextRange, bool)> { + let (_, range, group) = context.find_curly_group_word()?; + latex::LabelReferenceRange::cast(group.syntax().parent()?)?; + Some((range, false)) +} + #[cfg(test)] mod tests { use cstree::TextRange; @@ -193,4 +187,24 @@ mod tests { assert_eq!(item.range, TextRange::new(11.into(), 11.into())); } } + + #[test] + fn test_multi_word() { + let request = FeatureTester::builder() + .files(vec![("main.tex", "\\ref{foo}\\label{foo bar}")]) + .main("main.tex") + .line(0) + .character(8) + .build() + .completion(); + + let context = CursorContext::new(request); + let mut actual_items = Vec::new(); + complete_labels(&context, &mut actual_items, CancellationToken::none()); + + assert!(!actual_items.is_empty()); + for item in actual_items { + assert_eq!(item.range, TextRange::new(5.into(), 8.into())); + } + } } diff --git a/support/texlab/src/features/completion/theorem.rs b/support/texlab/src/features/completion/theorem.rs index 54db031a4b..25eea87ab7 100644 --- a/support/texlab/src/features/completion/theorem.rs +++ b/support/texlab/src/features/completion/theorem.rs @@ -1,11 +1,7 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; -use crate::{ - features::cursor::CursorContext, - syntax::{latex, CstNode}, -}; +use crate::features::cursor::CursorContext; use super::types::{InternalCompletionItem, InternalCompletionItemData}; @@ -18,18 +14,7 @@ pub fn complete_theorem_environments<'a>( return None; } - let token = context.cursor.as_latex()?; - let group = latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; - if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) { - return None; - } - - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; + let (_, range) = context.find_environment_name()?; for document in &context.request.subset.documents { if let Some(data) = document.data.as_latex() { @@ -49,6 +34,8 @@ pub fn complete_theorem_environments<'a>( #[cfg(test)] mod tests { + use cstree::TextRange; + use crate::features::testing::FeatureTester; use super::*; diff --git a/support/texlab/src/features/completion/tikz_library.rs b/support/texlab/src/features/completion/tikz_library.rs index da2a26cc9b..b9c5e3ec7a 100644 --- a/support/texlab/src/features/completion/tikz_library.rs +++ b/support/texlab/src/features/completion/tikz_library.rs @@ -1,5 +1,4 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; use crate::{ @@ -17,15 +16,8 @@ pub fn complete_tikz_libraries<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; + let (_, range, group) = context.find_curly_group_word_list()?; - let group = latex::CurlyGroupWordList::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; let import = latex::TikzLibraryImport::cast(group.syntax().parent()?)?; if import.command()?.text() == "\\usepgflibrary" { @@ -49,6 +41,8 @@ pub fn complete_tikz_libraries<'a>( #[cfg(test)] mod tests { + use cstree::TextRange; + use crate::features::testing::FeatureTester; use super::*; diff --git a/support/texlab/src/features/completion/types.rs b/support/texlab/src/features/completion/types.rs index b58a155fc4..b7e5c09592 100644 --- a/support/texlab/src/features/completion/types.rs +++ b/support/texlab/src/features/completion/types.rs @@ -40,7 +40,7 @@ pub enum InternalCompletionItemData<'a> { BeginCommand, Citation { uri: Arc<Uri>, - key: &'a str, + key: String, text: String, ty: Structure, }, @@ -67,10 +67,10 @@ pub enum InternalCompletionItemData<'a> { name: &'a str, }, Acronym { - name: &'a str, + name: String, }, GlossaryEntry { - name: &'a str, + name: String, }, File { name: SmolStr, @@ -79,7 +79,7 @@ pub enum InternalCompletionItemData<'a> { name: SmolStr, }, Label { - name: &'a str, + name: String, kind: Structure, header: Option<String>, footer: Option<String>, diff --git a/support/texlab/src/features/completion/user_environment.rs b/support/texlab/src/features/completion/user_environment.rs index a1a5701fac..5529143adc 100644 --- a/support/texlab/src/features/completion/user_environment.rs +++ b/support/texlab/src/features/completion/user_environment.rs @@ -1,11 +1,7 @@ use cancellation::CancellationToken; -use cstree::TextRange; use lsp_types::CompletionParams; -use crate::{ - features::cursor::CursorContext, - syntax::{latex, CstNode}, -}; +use crate::features::cursor::CursorContext; use super::types::{InternalCompletionItem, InternalCompletionItemData}; @@ -16,18 +12,7 @@ pub fn complete_user_environments<'a>( ) -> Option<()> { cancellation_token.result().ok()?; - let token = context.cursor.as_latex()?; - let group = latex::CurlyGroupWord::cast(token.parent()) - .filter(|group| context.is_inside_latex_curly(group))?; - if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) { - return None; - } - - let range = if token.kind() == latex::WORD { - token.text_range() - } else { - TextRange::empty(context.offset) - }; + let (name, range) = context.find_environment_name()?; for document in &context.request.subset.documents { if let Some(data) = document.data.as_latex() { @@ -35,7 +20,7 @@ pub fn complete_user_environments<'a>( .extras .environment_names .iter() - .filter(|name| name.as_str() != token.text()) + .filter(|n| n.as_str() != name) { cancellation_token.result().ok()?; items.push(InternalCompletionItem::new( |