diff options
Diffstat (limited to 'support/texlab/src/features')
30 files changed, 420 insertions, 355 deletions
diff --git a/support/texlab/src/features/completion.rs b/support/texlab/src/features/completion.rs index 13c7aad22b..29559bd808 100644 --- a/support/texlab/src/features/completion.rs +++ b/support/texlab/src/features/completion.rs @@ -19,6 +19,8 @@ mod user_command; mod user_environment; mod util; +use std::borrow::Cow; + use cancellation::CancellationToken; use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; use lsp_types::{ @@ -144,15 +146,29 @@ fn dedup(items: Vec<InternalCompletionItem>) -> Vec<InternalCompletionItem> { } fn score(context: &CursorContext<CompletionParams>, items: &mut Vec<InternalCompletionItem>) { - let pattern = match &context.cursor { - Cursor::Latex(token) if token.kind().is_command_name() => token.text().trim_end(), - Cursor::Latex(token) if token.kind() == latex::WORD => token.text(), - Cursor::Latex(_) => "", - Cursor::Bibtex(token) if token.kind().is_type() => token.text(), - Cursor::Bibtex(token) if token.kind() == bibtex::WORD => token.text(), - Cursor::Bibtex(token) if token.kind() == bibtex::COMMAND_NAME => token.text().trim_end(), - Cursor::Bibtex(_) => "", - Cursor::Nothing => "", + let pattern: Cow<str> = match &context.cursor { + Cursor::Latex(token) if token.kind().is_command_name() => token.text().trim_end().into(), + Cursor::Latex(token) if token.kind() == latex::WORD => { + if let Some(key) = latex::Key::cast(token.parent()) { + key.to_string().into() + } else { + token.text().into() + } + } + Cursor::Latex(_) => "".into(), + Cursor::Bibtex(token) if token.kind().is_type() => token.text().into(), + Cursor::Bibtex(token) if token.kind() == bibtex::WORD => { + if let Some(key) = bibtex::Key::cast(token.parent()) { + key.to_string().into() + } else { + token.text().into() + } + } + Cursor::Bibtex(token) if token.kind() == bibtex::COMMAND_NAME => { + token.text().trim_end().into() + } + Cursor::Bibtex(_) => "".into(), + Cursor::Nothing => "".into(), }; let file_pattern = pattern.split('/').last().unwrap(); @@ -211,7 +227,7 @@ fn preselect( let group = latex::CurlyGroupWord::cast(name.parent())?; let end = latex::End::cast(group.syntax().parent()?)?; let environment = latex::Environment::cast(end.syntax().parent()?)?; - let name = environment.begin()?.name()?.word()?.text(); + let name = environment.begin()?.name()?.key()?.to_string(); for item in items { if item.data.label() == name { 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( diff --git a/support/texlab/src/features/cursor.rs b/support/texlab/src/features/cursor.rs index 19079b841b..6e8719987a 100644 --- a/support/texlab/src/features/cursor.rs +++ b/support/texlab/src/features/cursor.rs @@ -1,7 +1,7 @@ use cstree::{TextRange, TextSize}; use lsp_types::{ - CompletionParams, GotoDefinitionParams, HoverParams, Position, ReferenceParams, RenameParams, - TextDocumentPositionParams, + CompletionParams, DocumentHighlightParams, GotoDefinitionParams, HoverParams, Position, + ReferenceParams, RenameParams, TextDocumentPositionParams, }; use crate::{ @@ -42,6 +42,14 @@ impl Cursor { return Some(Self::Latex(right)); } + if left.kind() == latex::WHITESPACE && left.parent().kind() == latex::KEY { + return Some(Self::Latex(left)); + } + + if right.kind() == latex::WHITESPACE && right.parent().kind() == latex::KEY { + return Some(Self::Latex(right)); + } + Some(Self::Latex(right)) } @@ -149,66 +157,116 @@ impl<P: HasPosition> CursorContext<P> { group.small_range().contains(self.offset) || group.right_curly().is_none() } - pub fn find_citation_key_word(&self) -> Option<(&str, TextRange)> { - let key = self + pub fn find_citation_key_word(&self) -> Option<(String, TextRange)> { + let word = self .cursor .as_latex() .filter(|token| token.kind() == latex::WORD)?; - let group = latex::CurlyGroupWordList::cast(key.parent())?; + let key = latex::Key::cast(word.parent())?; + + let group = latex::CurlyGroupWordList::cast(key.syntax().parent()?)?; latex::Citation::cast(group.syntax().parent()?)?; - Some((key.text(), key.text_range())) + Some((key.to_string(), key.small_range())) } - pub fn find_citation_key_command(&self) -> Option<(&str, TextRange)> { + pub fn find_citation_key_command(&self) -> Option<(String, TextRange)> { let command = self.cursor.as_latex()?; let citation = latex::Citation::cast(command.parent())?; - let key = citation.key_list()?.words().next()?; - Some((key.text(), key.text_range())) + let key = citation.key_list()?.keys().next()?; + Some((key.to_string(), key.small_range())) } - pub fn find_entry_key(&self) -> Option<(&str, TextRange)> { - let key = self + pub fn find_entry_key(&self) -> Option<(String, TextRange)> { + let word = self .cursor .as_bibtex() .filter(|token| token.kind() == bibtex::WORD)?; - bibtex::Entry::cast(key.parent())?; - Some((key.text(), key.text_range())) + let key = bibtex::Key::cast(word.parent())?; + + bibtex::Entry::cast(key.syntax().parent()?)?; + Some((key.to_string(), key.small_range())) } - pub fn find_label_name_word(&self) -> Option<(&str, TextRange)> { + pub fn find_label_name_key(&self) -> Option<(String, TextRange)> { let name = self .cursor .as_latex() .filter(|token| token.kind() == latex::WORD)?; + let key = latex::Key::cast(name.parent())?; + if matches!( - name.parent().parent()?.kind(), + key.syntax().parent()?.parent()?.kind(), latex::LABEL_DEFINITION | latex::LABEL_REFERENCE | latex::LABEL_REFERENCE_RANGE ) { - Some((name.text(), name.text_range())) + Some((key.to_string(), key.small_range())) } else { None } } - pub fn find_label_name_command(&self) -> Option<(&str, TextRange)> { + pub fn find_label_name_command(&self) -> Option<(String, TextRange)> { let node = self.cursor.as_latex()?.parent(); if let Some(label) = latex::LabelDefinition::cast(node) { - let name = label.name()?.word()?; - Some((name.text(), name.text_range())) + let name = label.name()?.key()?; + Some((name.to_string(), name.small_range())) } else if let Some(label) = latex::LabelReference::cast(node) { - let name = label.name_list()?.words().next()?; - Some((name.text(), name.text_range())) + let name = label.name_list()?.keys().next()?; + Some((name.to_string(), name.small_range())) } else if let Some(label) = latex::LabelReferenceRange::cast(node) { - let name = label.from()?.word()?; - Some((name.text(), name.text_range())) + let name = label.from()?.key()?; + Some((name.to_string(), name.small_range())) } else { None } } + + pub fn find_environment_name(&self) -> Option<(String, TextRange)> { + let (name, range, group) = self.find_curly_group_word()?; + + if !matches!(group.syntax().parent()?.kind(), latex::BEGIN | latex::END) { + return None; + } + + Some((name, range)) + } + + pub fn find_curly_group_word(&self) -> Option<(String, TextRange, latex::CurlyGroupWord)> { + let token = self.cursor.as_latex()?; + let key = latex::Key::cast(token.parent()); + + let group = key + .as_ref() + .and_then(|key| key.syntax().parent()) + .unwrap_or(token.parent()); + + let group = + latex::CurlyGroupWord::cast(group).filter(|group| self.is_inside_latex_curly(group))?; + + key.map(|key| (key.to_string(), key.small_range(), group)) + .or_else(|| Some((String::new(), TextRange::empty(self.offset), group))) + } + + pub fn find_curly_group_word_list( + &self, + ) -> Option<(String, TextRange, latex::CurlyGroupWordList)> { + let token = self.cursor.as_latex()?; + let key = latex::Key::cast(token.parent()); + + let group = key + .as_ref() + .and_then(|key| key.syntax().parent()) + .unwrap_or(token.parent()); + + let group = latex::CurlyGroupWordList::cast(group) + .filter(|group| self.is_inside_latex_curly(group))?; + + key.map(|key| (key.to_string(), key.small_range(), group)) + .or_else(|| Some((String::new(), TextRange::empty(self.offset), group))) + } } pub trait HasPosition { @@ -250,3 +308,9 @@ impl HasPosition for GotoDefinitionParams { self.text_document_position_params.position } } + +impl HasPosition for DocumentHighlightParams { + fn position(&self) -> Position { + self.text_document_position_params.position + } +} diff --git a/support/texlab/src/features/definition/entry.rs b/support/texlab/src/features/definition/entry.rs index 7f489ec848..b04490dc32 100644 --- a/support/texlab/src/features/definition/entry.rs +++ b/support/texlab/src/features/definition/entry.rs @@ -13,29 +13,31 @@ pub fn goto_entry_definition( ) -> Option<Vec<LocationLink>> { let main_document = context.request.main_document(); - let key = context + let word = context .cursor .as_latex() .filter(|token| token.kind() == latex::WORD)?; - latex::Citation::cast(key.parent().parent()?)?; + let key = latex::Key::cast(word.parent())?; + + latex::Citation::cast(key.syntax().parent()?.parent()?)?; let origin_selection_range = main_document .line_index - .line_col_lsp_range(key.text_range()); + .line_col_lsp_range(key.small_range()); 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) { cancellation_token.result().ok()?; - if let Some(key) = entry.key().filter(|k| k.text() == key.text()) { + if let Some(key) = entry.key().filter(|k| k.to_string() == key.to_string()) { return Some(vec![LocationLink { origin_selection_range: Some(origin_selection_range), target_uri: document.uri.as_ref().clone().into(), target_selection_range: document .line_index - .line_col_lsp_range(key.text_range()), + .line_col_lsp_range(key.small_range()), target_range: document.line_index.line_col_lsp_range(entry.small_range()), }]); } diff --git a/support/texlab/src/features/definition/label.rs b/support/texlab/src/features/definition/label.rs index 65f0981de0..fd30b1fbc5 100644 --- a/support/texlab/src/features/definition/label.rs +++ b/support/texlab/src/features/definition/label.rs @@ -1,7 +1,10 @@ use cancellation::CancellationToken; use lsp_types::{GotoDefinitionParams, LocationLink}; -use crate::{features::cursor::CursorContext, find_label_definition, render_label, LineIndexExt}; +use crate::{ + features::cursor::CursorContext, find_label_definition, render_label, syntax::CstNode, + LineIndexExt, +}; pub fn goto_label_definition( context: &CursorContext<GotoDefinitionParams>, @@ -10,7 +13,7 @@ pub fn goto_label_definition( let main_document = context.request.main_document(); let (name_text, name_range) = context - .find_label_name_word() + .find_label_name_key() .or_else(|| context.find_label_name_command())?; let origin_selection_range = main_document.line_index.line_col_lsp_range(name_range); @@ -18,10 +21,10 @@ pub fn goto_label_definition( for document in &context.request.subset.documents { cancellation_token.result().ok()?; if let Some(data) = document.data.as_latex() { - if let Some(definition) = find_label_definition(&data.root, name_text) { - let target_selection_range = definition.name()?.word()?.text_range(); + if let Some(definition) = find_label_definition(&data.root, &name_text) { + let target_selection_range = definition.name()?.key()?.small_range(); let target_range = - render_label(&context.request.subset, name_text, Some(definition)) + render_label(&context.request.subset, &name_text, Some(definition)) .map(|label| label.range) .unwrap_or(target_selection_range); diff --git a/support/texlab/src/features/formatting.rs b/support/texlab/src/features/formatting.rs index 4492a0532d..c7c2dc410a 100644 --- a/support/texlab/src/features/formatting.rs +++ b/support/texlab/src/features/formatting.rs @@ -4,7 +4,7 @@ mod latexindent; use cancellation::CancellationToken; use lsp_types::{DocumentFormattingParams, TextEdit}; -use crate::BibtexFormatter; +use crate::{BibtexFormatter, LatexFormatter}; use self::{bibtex_internal::format_bibtex_internal, latexindent::format_with_latexindent}; @@ -19,6 +19,10 @@ pub fn format_source_code( edits = edits.or_else(|| format_bibtex_internal(&request, cancellation_token)); } + if request.context.options.read().unwrap().latex_formatter == LatexFormatter::Texlab { + edits = edits.or_else(|| Some(vec![])); + } + edits = edits.or_else(|| format_with_latexindent(&request, cancellation_token)); edits } diff --git a/support/texlab/src/features/formatting/bibtex_internal.rs b/support/texlab/src/features/formatting/bibtex_internal.rs index 8481f561e7..641bee0cfe 100644 --- a/support/texlab/src/features/formatting/bibtex_internal.rs +++ b/support/texlab/src/features/formatting/bibtex_internal.rs @@ -148,7 +148,7 @@ impl<'a> Formatter<'a> { self.visit_token_lowercase(entry.ty().unwrap()); self.output.push('{'); if let Some(key) = entry.key() { - self.output.push_str(key.text()); + self.output.push_str(&key.to_string()); self.output.push(','); self.output.push('\n'); for field in entry.fields() { diff --git a/support/texlab/src/features/formatting/latexindent.rs b/support/texlab/src/features/formatting/latexindent.rs index f603728a5d..300f512578 100644 --- a/support/texlab/src/features/formatting/latexindent.rs +++ b/support/texlab/src/features/formatting/latexindent.rs @@ -1,50 +1,98 @@ use std::{ - io::{BufWriter, Write}, + fs, process::{Command, Stdio}, }; use cancellation::CancellationToken; use cstree::{TextLen, TextRange}; use lsp_types::{DocumentFormattingParams, TextEdit}; +use tempfile::tempdir; -use crate::{features::FeatureRequest, LineIndexExt}; +use crate::{features::FeatureRequest, DocumentLanguage, LineIndexExt}; pub fn format_with_latexindent( request: &FeatureRequest<DocumentFormattingParams>, _cancellation_token: &CancellationToken, ) -> Option<Vec<TextEdit>> { + let directory = tempdir().ok()?; let document = request.main_document(); - let current_dir = &request.context.current_directory; let options = request.context.options.read().unwrap(); - let current_dir = match &options.root_directory { - Some(root_directory) => current_dir.join(root_directory), - None => current_dir.clone(), + let current_dir = options + .root_directory + .as_ref() + .cloned() + .or_else(|| { + if document.uri.scheme() == "file" { + document + .uri + .to_file_path() + .unwrap() + .parent() + .map(ToOwned::to_owned) + } else { + None + } + }) + .unwrap_or_else(|| ".".into()); + + let local = match &options.latexindent.local { + Some(local) => format!("--local={}", local), + None => "-l".to_string(), }; + + let modify_line_breaks = options.latexindent.modify_line_breaks; + drop(options); - let mut process = Command::new("latexindent") - .arg("-l") + let path = directory.path(); + let _ = fs::copy( + current_dir.join("localSettings.yaml"), + path.join("localSettings.yaml"), + ); + let _ = fs::copy( + current_dir.join(".localSettings.yaml"), + path.join(".localSettings.yaml"), + ); + let _ = fs::copy( + current_dir.join("latexindent.yaml"), + path.join("latexindent.yaml"), + ); + + let name = if document.language() == DocumentLanguage::Bibtex { + "file.bib" + } else { + "file.tex" + }; + + fs::write(directory.path().join(name), &document.text).ok()?; + + let mut args = Vec::new(); + if modify_line_breaks { + args.push("--modifylinebreaks"); + } + args.push(&local); + args.push(name); + + let output = Command::new("latexindent") + .args(&args) .current_dir(current_dir) - .stdin(Stdio::piped()) + .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()) - .spawn() + .current_dir(directory.path()) + .output() .ok()?; - let stdin = process.stdin.take()?; - let mut stdin = BufWriter::new(stdin); - stdin.write_all(document.text.as_bytes()).ok()?; - drop(stdin); - - let output = process.wait_with_output().ok()?; - let new_text = String::from_utf8_lossy(&output.stdout).into_owned(); - - Some(vec![TextEdit { - range: document - .line_index - .line_col_lsp_range(TextRange::new(0.into(), document.text.text_len())), - new_text, - }]) + if new_text.is_empty() { + None + } else { + Some(vec![TextEdit { + range: document + .line_index + .line_col_lsp_range(TextRange::new(0.into(), document.text.text_len())), + new_text, + }]) + } } diff --git a/support/texlab/src/features/highlight.rs b/support/texlab/src/features/highlight.rs index e1aea6e2ad..386018f353 100644 --- a/support/texlab/src/features/highlight.rs +++ b/support/texlab/src/features/highlight.rs @@ -5,11 +5,12 @@ use lsp_types::{DocumentHighlight, DocumentHighlightParams}; use self::label::find_label_highlights; -use super::FeatureRequest; +use super::{cursor::CursorContext, FeatureRequest}; pub fn find_document_highlights( request: FeatureRequest<DocumentHighlightParams>, - token: &CancellationToken, + cancellation_token: &CancellationToken, ) -> Option<Vec<DocumentHighlight>> { - find_label_highlights(&request, token) + let context = CursorContext::new(request); + find_label_highlights(&context, cancellation_token) } diff --git a/support/texlab/src/features/highlight/label.rs b/support/texlab/src/features/highlight/label.rs index 4c94180f2a..9694993033 100644 --- a/support/texlab/src/features/highlight/label.rs +++ b/support/texlab/src/features/highlight/label.rs @@ -2,48 +2,32 @@ use cancellation::CancellationToken; use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams}; use crate::{ - features::FeatureRequest, + features::cursor::CursorContext, syntax::{latex, CstNode}, LineIndexExt, }; pub fn find_label_highlights( - request: &FeatureRequest<DocumentHighlightParams>, - token: &CancellationToken, + context: &CursorContext<DocumentHighlightParams>, + cancellation_token: &CancellationToken, ) -> Option<Vec<DocumentHighlight>> { - let main_document = request.main_document(); - - let offset = main_document - .line_index - .offset_lsp(request.params.text_document_position_params.position); + let (name_text, _) = context.find_label_name_key()?; + let main_document = context.request.main_document(); let data = main_document.data.as_latex()?; - let name = data.root.token_at_offset(offset).right_biased()?; - if name.kind() != latex::WORD { - return None; - } - - if !matches!( - name.parent().parent()?.kind(), - latex::LABEL_DEFINITION | latex::LABEL_REFERENCE | latex::LABEL_REFERENCE_RANGE - ) { - return None; - } let mut highlights = Vec::new(); for node in data.root.descendants() { - if token.is_canceled() { - return None; - } + cancellation_token.result().ok()?; if let Some(label_name) = latex::LabelDefinition::cast(node) .and_then(|label| label.name()) - .and_then(|label_name| label_name.word()) - .filter(|label_name| label_name.text() == name.text()) + .and_then(|label_name| label_name.key()) + .filter(|label_name| label_name.to_string() == name_text) { let range = main_document .line_index - .line_col_lsp_range(label_name.text_range()); + .line_col_lsp_range(label_name.small_range()); highlights.push(DocumentHighlight { range, @@ -53,12 +37,12 @@ pub fn find_label_highlights( for label_name in label .name_list() .into_iter() - .flat_map(|name| name.words()) - .filter(|label_name| label_name.text() == name.text()) + .flat_map(|name| name.keys()) + .filter(|label_name| label_name.to_string() == name_text) { let range = main_document .line_index - .line_col_lsp_range(label_name.text_range()); + .line_col_lsp_range(label_name.small_range()); highlights.push(DocumentHighlight { range, @@ -68,12 +52,12 @@ pub fn find_label_highlights( } else if let Some(label) = latex::LabelReferenceRange::cast(node) { if let Some(label_name) = label .from() - .and_then(|label_name| label_name.word()) - .filter(|label_name| label_name.text() == name.text()) + .and_then(|label_name| label_name.key()) + .filter(|label_name| label_name.to_string() == name_text) { let range = main_document .line_index - .line_col_lsp_range(label_name.text_range()); + .line_col_lsp_range(label_name.small_range()); highlights.push(DocumentHighlight { range, @@ -83,12 +67,12 @@ pub fn find_label_highlights( if let Some(label_name) = label .to() - .and_then(|label_name| label_name.word()) - .filter(|label_name| label_name.text() == name.text()) + .and_then(|label_name| label_name.key()) + .filter(|label_name| label_name.to_string() == name_text) { let range = main_document .line_index - .line_col_lsp_range(label_name.text_range()); + .line_col_lsp_range(label_name.small_range()); highlights.push(DocumentHighlight { range, @@ -118,8 +102,9 @@ mod tests { .character(0) .build() .highlight(); + let context = CursorContext::new(request); - let actual_links = find_label_highlights(&request, CancellationToken::none()); + let actual_links = find_label_highlights(&context, CancellationToken::none()); assert!(actual_links.is_none()); } @@ -133,8 +118,9 @@ mod tests { .character(0) .build() .highlight(); + let context = CursorContext::new(request); - let actual_links = find_label_highlights(&request, CancellationToken::none()); + let actual_links = find_label_highlights(&context, CancellationToken::none()); assert!(actual_links.is_none()); } @@ -148,8 +134,9 @@ mod tests { .character(7) .build(); let request = tester.highlight(); + let context = CursorContext::new(request); - let actual_highlights = find_label_highlights(&request, CancellationToken::none()).unwrap(); + let actual_highlights = find_label_highlights(&context, CancellationToken::none()).unwrap(); let expected_highlights = vec![ DocumentHighlight { diff --git a/support/texlab/src/features/hover/label.rs b/support/texlab/src/features/hover/label.rs index 79afe7d250..26477feb7c 100644 --- a/support/texlab/src/features/hover/label.rs +++ b/support/texlab/src/features/hover/label.rs @@ -10,10 +10,10 @@ pub fn find_label_hover( let main_document = context.request.main_document(); let (name_text, name_range) = context - .find_label_name_word() + .find_label_name_key() .or_else(|| context.find_label_name_command())?; - let label = render_label(&context.request.subset, name_text, None)?; + let label = render_label(&context.request.subset, &name_text, None)?; Some(Hover { range: Some(main_document.line_index.line_col_lsp_range(name_range)), diff --git a/support/texlab/src/features/reference/entry.rs b/support/texlab/src/features/reference/entry.rs index fbbcbdb4cc..a9e15ba5b6 100644 --- a/support/texlab/src/features/reference/entry.rs +++ b/support/texlab/src/features/reference/entry.rs @@ -26,9 +26,9 @@ pub fn find_entry_references( .descendants() .filter_map(latex::Citation::cast) .filter_map(|citation| citation.key_list()) - .flat_map(|keys| keys.words()) - .filter(|key| key.text() == key_text) - .map(|key| document.line_index.line_col_lsp_range(key.text_range())) + .flat_map(|keys| keys.keys()) + .filter(|key| key.to_string() == key_text) + .map(|key| document.line_index.line_col_lsp_range(key.small_range())) .for_each(|range| { references.push(Location::new(document.uri.as_ref().clone().into(), range)); }); @@ -38,8 +38,8 @@ pub fn find_entry_references( .children() .filter_map(bibtex::Entry::cast) .filter_map(|entry| entry.key()) - .filter(|key| key.text() == key_text) - .map(|key| document.line_index.line_col_lsp_range(key.text_range())) + .filter(|key| key.to_string() == key_text) + .map(|key| document.line_index.line_col_lsp_range(key.small_range())) .for_each(|range| { references.push(Location::new(document.uri.as_ref().clone().into(), range)); }); diff --git a/support/texlab/src/features/reference/label.rs b/support/texlab/src/features/reference/label.rs index e9aa3a8510..45c6b90137 100644 --- a/support/texlab/src/features/reference/label.rs +++ b/support/texlab/src/features/reference/label.rs @@ -11,7 +11,7 @@ pub fn find_label_references( cancellation_token.result().ok()?; let (name_text, _) = context - .find_label_name_word() + .find_label_name_key() .or_else(|| context.find_label_name_command())?; for document in &context.request.subset.documents { diff --git a/support/texlab/src/features/rename/entry.rs b/support/texlab/src/features/rename/entry.rs index 14fbfd42d7..35a77d926e 100644 --- a/support/texlab/src/features/rename/entry.rs +++ b/support/texlab/src/features/rename/entry.rs @@ -4,7 +4,7 @@ use cancellation::CancellationToken; use lsp_types::{Range, RenameParams, TextEdit, WorkspaceEdit}; use crate::{ - features::cursor::{Cursor, CursorContext, HasPosition}, + features::cursor::{CursorContext, HasPosition}, syntax::{bibtex, latex, CstNode}, DocumentData, LineIndexExt, }; @@ -13,18 +13,9 @@ pub fn prepare_entry_rename<P: HasPosition>( context: &CursorContext<P>, _cancellation_token: &CancellationToken, ) -> Option<Range> { - let range = match &context.cursor { - Cursor::Latex(token) if token.kind() == latex::WORD => { - let group = latex::CurlyGroupWordList::cast(token.parent())?; - latex::Citation::cast(group.syntax().parent()?)?; - token.text_range() - } - Cursor::Bibtex(token) if token.kind() == bibtex::WORD => { - bibtex::Entry::cast(token.parent())?; - token.text_range() - } - _ => return None, - }; + let (_, range) = context + .find_citation_key_word() + .or_else(|| context.find_entry_key())?; Some( context @@ -41,11 +32,9 @@ pub fn rename_entry( ) -> Option<WorkspaceEdit> { cancellation_token.result().ok()?; prepare_entry_rename(context, cancellation_token)?; - let key_text = context - .cursor - .as_latex() - .map(|token| token.text()) - .or_else(|| context.cursor.as_bibtex().map(|token| token.text()))?; + let (key_text, _) = context + .find_citation_key_word() + .or_else(|| context.find_entry_key())?; let mut changes = HashMap::new(); for document in &context.request.subset.documents { @@ -57,9 +46,9 @@ pub fn rename_entry( .descendants() .filter_map(latex::Citation::cast) .filter_map(|citation| citation.key_list()) - .flat_map(|keys| keys.words()) - .filter(|key| key.text() == key_text) - .map(|key| document.line_index.line_col_lsp_range(key.text_range())) + .flat_map(|keys| keys.keys()) + .filter(|key| key.to_string() == key_text) + .map(|key| document.line_index.line_col_lsp_range(key.small_range())) .map(|range| TextEdit::new(range, context.request.params.new_name.clone())) .collect(); changes.insert(document.uri.as_ref().clone().into(), edits); @@ -70,8 +59,8 @@ pub fn rename_entry( .descendants() .filter_map(bibtex::Entry::cast) .filter_map(|entry| entry.key()) - .filter(|key| key.text() == key_text) - .map(|key| document.line_index.line_col_lsp_range(key.text_range())) + .filter(|key| key.to_string() == key_text) + .map(|key| document.line_index.line_col_lsp_range(key.small_range())) .map(|range| TextEdit::new(range, context.request.params.new_name.clone())) .collect(); changes.insert(document.uri.as_ref().clone().into(), edits); diff --git a/support/texlab/src/features/rename/label.rs b/support/texlab/src/features/rename/label.rs index 11b414ccfa..a73a0064bf 100644 --- a/support/texlab/src/features/rename/label.rs +++ b/support/texlab/src/features/rename/label.rs @@ -13,20 +13,14 @@ pub fn prepare_label_rename<P: HasPosition>( context: &CursorContext<P>, _cancellation_token: &CancellationToken, ) -> Option<Range> { - let name = context.cursor.as_latex()?; - name.parent().parent().filter(|node| { - matches!( - node.kind(), - latex::LABEL_DEFINITION | latex::LABEL_REFERENCE | latex::LABEL_REFERENCE_RANGE - ) - })?; + let (_, range) = context.find_label_name_key()?; Some( context .request .main_document() .line_index - .line_col_lsp_range(name.text_range()), + .line_col_lsp_range(range), ) } @@ -35,7 +29,8 @@ pub fn rename_label( cancellation_token: &CancellationToken, ) -> Option<WorkspaceEdit> { prepare_label_rename(context, cancellation_token)?; - let name_text = context.cursor.as_latex()?.text(); + let (name_text, _) = context.find_label_name_key()?; + let mut changes = HashMap::new(); for document in &context.request.subset.documents { cancellation_token.result().ok()?; @@ -44,9 +39,9 @@ pub fn rename_label( for node in data.root.descendants() { if let Some(range) = latex::LabelDefinition::cast(node) .and_then(|label| label.name()) - .and_then(|name| name.word()) - .filter(|name| name.text() == name_text) - .map(|name| document.line_index.line_col_lsp_range(name.text_range())) + .and_then(|name| name.key()) + .filter(|name| name.to_string() == name_text) + .map(|name| document.line_index.line_col_lsp_range(name.small_range())) { edits.push(TextEdit::new( range, @@ -57,9 +52,9 @@ pub fn rename_label( latex::LabelReference::cast(node) .and_then(|label| label.name_list()) .into_iter() - .flat_map(|label| label.words()) - .filter(|name| name.text() == name_text) - .map(|name| document.line_index.line_col_lsp_range(name.text_range())) + .flat_map(|label| label.keys()) + .filter(|name| name.to_string() == name_text) + .map(|name| document.line_index.line_col_lsp_range(name.small_range())) .for_each(|range| { edits.push(TextEdit::new( range, @@ -70,22 +65,22 @@ pub fn rename_label( if let Some(label) = latex::LabelReferenceRange::cast(node) { if let Some(name1) = label .from() - .and_then(|name| name.word()) - .filter(|name| name.text() == name_text) + .and_then(|name| name.key()) + .filter(|name| name.to_string() == name_text) { edits.push(TextEdit::new( - document.line_index.line_col_lsp_range(name1.text_range()), + document.line_index.line_col_lsp_range(name1.small_range()), context.request.params.new_name.clone(), )); } if let Some(name2) = label .from() - .and_then(|name| name.word()) - .filter(|name| name.text() == name_text) + .and_then(|name| name.key()) + .filter(|name| name.to_string() == name_text) { edits.push(TextEdit::new( - document.line_index.line_col_lsp_range(name2.text_range()), + document.line_index.line_col_lsp_range(name2.small_range()), context.request.params.new_name.clone(), )); } diff --git a/support/texlab/src/features/symbol/bibtex.rs b/support/texlab/src/features/symbol/bibtex.rs index e030195880..db555a7176 100644 --- a/support/texlab/src/features/symbol/bibtex.rs +++ b/support/texlab/src/features/symbol/bibtex.rs @@ -68,7 +68,7 @@ pub fn find_bibtex_symbols( .unwrap_or(BibtexEntryTypeCategory::Misc); buf.push(InternalSymbol { - name: key.text().to_string(), + name: key.to_string(), label: None, kind: InternalSymbolKind::Entry(category), deprecated: false, @@ -77,7 +77,7 @@ pub fn find_bibtex_symbols( .line_col_lsp_range(entry.small_range()), selection_range: main_document .line_index - .line_col_lsp_range(key.text_range()), + .line_col_lsp_range(key.small_range()), children, }); } diff --git a/support/texlab/src/features/symbol/latex.rs b/support/texlab/src/features/symbol/latex.rs index ce584c0c76..ec549c87fe 100644 --- a/support/texlab/src/features/symbol/latex.rs +++ b/support/texlab/src/features/symbol/latex.rs @@ -59,25 +59,25 @@ fn visit(context: &mut Context, node: &latex::SyntaxNode) -> Vec<InternalSymbol> latex::ENVIRONMENT => latex::Environment::cast(node) .and_then(|env| env.begin()) .and_then(|begin| begin.name()) - .and_then(|name| name.word()) - .map(|name| name.text()) + .and_then(|name| name.key()) + .map(|name| name.to_string()) .and_then(|name| { if LANGUAGE_DATA .math_environments .iter() - .any(|env| env == name) + .any(|env| env == &name) { visit_equation_environment(context, node) } else if LANGUAGE_DATA .enum_environments .iter() - .any(|env| env == name) + .any(|env| env == &name) { - visit_enumeration(context, node, name) - } else if let Ok(float_kind) = LabelledFloatKind::from_str(name) { + visit_enumeration(context, node, &name) + } else if let Ok(float_kind) = LabelledFloatKind::from_str(&name) { visit_float(context, node, float_kind) } else { - visit_theorem(context, node, name) + visit_theorem(context, node, &name) } }), _ => None, @@ -161,12 +161,12 @@ fn visit_enum_item(context: &mut Context, node: &latex::SyntaxNode) -> Option<In .filter_map(latex::Environment::cast) .filter_map(|environment| environment.begin()) .filter_map(|begin| begin.name()) - .filter_map(|name| name.word()) + .filter_map(|name| name.key()) .any(|name| { LANGUAGE_DATA .enum_environments .iter() - .any(|e| e == name.text()) + .any(|e| e == &name.to_string()) }) { return None; @@ -471,7 +471,7 @@ fn find_label_by_parent( ) -> Option<NumberedLabel> { let node = parent.children().find_map(latex::LabelDefinition::cast)?; - let name = node.name()?.word()?.text(); + let name = node.name()?.key()?.to_string(); let range = context .subset .documents @@ -479,7 +479,7 @@ fn find_label_by_parent( .line_index .line_col_lsp_range(node.small_range()); - let number = find_label_number(&context.subset, name); + let number = find_label_number(&context.subset, &name); Some(NumberedLabel { name: name.to_string(), range, |