From 7a98fba74f3b17789624763bfb7a93e5cdeb6c7e Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Wed, 5 Jun 2024 17:15:34 +0200 Subject: [PATCH 01/21] Initial work towards prefixes for custom label commands. This commit mainly extends the configs with a map from label command name to the respective prefix. It does not yet implement any functionality, since it appears that `base-db/semantics/label` needs an extension carrying a span that contains the label command. The plan is to extend the logic in `crates/diagnostics/src/labels.rs`. --- crates/diagnostics/src/labels.rs | 2 ++ crates/parser/src/config.rs | 20 +++++++++++++++++++- crates/texlab/src/server/options.rs | 5 ++++- crates/texlab/src/util/from_proto.rs | 10 ++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/diagnostics/src/labels.rs b/crates/diagnostics/src/labels.rs index 15b5aaad..f7e07211 100644 --- a/crates/diagnostics/src/labels.rs +++ b/crates/diagnostics/src/labels.rs @@ -13,6 +13,8 @@ pub fn detect_undefined_and_unused_labels( workspace: &Workspace, results: &mut FxHashMap>, ) { + let def_prefs = &workspace.config().syntax.label_definition_prefixes; + let ref_prefs = &workspace.config().syntax.label_reference_prefixes; for document in workspace.iter() { let DocumentData::Tex(data) = &document.data else { continue; diff --git a/crates/parser/src/config.rs b/crates/parser/src/config.rs index f81083f1..9bded322 100644 --- a/crates/parser/src/config.rs +++ b/crates/parser/src/config.rs @@ -1,4 +1,4 @@ -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; #[derive(Debug)] pub struct SyntaxConfig { @@ -8,7 +8,9 @@ pub struct SyntaxConfig { pub verbatim_environments: FxHashSet, pub citation_commands: FxHashSet, pub label_definition_commands: FxHashSet, + pub label_definition_prefixes: FxHashMap, pub label_reference_commands: FxHashSet, + pub label_reference_prefixes: FxHashMap, } impl Default for SyntaxConfig { @@ -38,11 +40,21 @@ impl Default for SyntaxConfig { .map(ToString::to_string) .collect(); + let label_definition_prefixes = DEFAULT_LABEL_DEFINITION_PREFIXES + .iter() + .map(|(x, y)| (ToString::to_string(x), ToString::to_string(y))) + .collect(); + let label_reference_commands = DEFAULT_LABEL_REFERENCE_COMMANDS .iter() .map(ToString::to_string) .collect(); + let label_reference_prefixes = DEFAULT_LABEL_REFERENCE_PREFIXES + .iter() + .map(|(x, y)| (ToString::to_string(x), ToString::to_string(y))) + .collect(); + Self { follow_package_links: false, math_environments, @@ -50,7 +62,9 @@ impl Default for SyntaxConfig { verbatim_environments, citation_commands, label_definition_commands, + label_definition_prefixes, label_reference_commands, + label_reference_prefixes, } } } @@ -172,6 +186,8 @@ static DEFAULT_CITATION_COMMANDS: &[&str] = &[ static DEFAULT_LABEL_DEFINITION_COMMANDS: &[&str] = &["label"]; +static DEFAULT_LABEL_DEFINITION_PREFIXES: &[(&str, &str)] = &[]; + static DEFAULT_LABEL_REFERENCE_COMMANDS: &[&str] = &[ "ref", "vref", @@ -192,3 +208,5 @@ static DEFAULT_LABEL_REFERENCE_COMMANDS: &[&str] = &[ "labelcpageref", "eqref", ]; + +static DEFAULT_LABEL_REFERENCE_PREFIXES: &[(&str, &str)] = &[]; diff --git a/crates/texlab/src/server/options.rs b/crates/texlab/src/server/options.rs index 3f50faef..1640c9cf 100644 --- a/crates/texlab/src/server/options.rs +++ b/crates/texlab/src/server/options.rs @@ -1,6 +1,7 @@ -use regex::Regex; use serde::{Deserialize, Serialize}; +use regex::Regex; + #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(default)] @@ -125,7 +126,9 @@ pub struct ExperimentalOptions { pub verbatim_environments: Vec, pub citation_commands: Vec, pub label_definition_commands: Vec, + pub label_definition_prefixes: Vec<(String, String)>, pub label_reference_commands: Vec, + pub label_reference_prefixes: Vec<(String, String)>, } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] diff --git a/crates/texlab/src/util/from_proto.rs b/crates/texlab/src/util/from_proto.rs index 789edbf9..01fd6518 100644 --- a/crates/texlab/src/util/from_proto.rs +++ b/crates/texlab/src/util/from_proto.rs @@ -356,10 +356,20 @@ pub fn config(value: Options) -> Config { .label_definition_commands .extend(value.experimental.label_definition_commands); + config + .syntax + .label_definition_prefixes + .extend(value.experimental.label_definition_prefixes); + config .syntax .label_reference_commands .extend(value.experimental.label_reference_commands); + config + .syntax + .label_reference_prefixes + .extend(value.experimental.label_reference_prefixes); + config } From 21a0266cf5b62ad92b585a92281e86f1825ffc22 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Mon, 10 Jun 2024 14:06:18 +0200 Subject: [PATCH 02/21] Prepend user-defined prefix to labels. This commit prepends user-defined prefixes to labels in the semantics pass. Note that this yields to a mismatch between the actual length of a span and its range, but there are no (anticipated) bad side-effects due to this. --- crates/base-db/src/document.rs | 5 ++- crates/base-db/src/semantics/tex.rs | 61 ++++++++++++++++++++++------- crates/diagnostics/src/labels.rs | 2 - 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/crates/base-db/src/document.rs b/crates/base-db/src/document.rs index 7a388129..afb5869d 100644 --- a/crates/base-db/src/document.rs +++ b/crates/base-db/src/document.rs @@ -56,7 +56,10 @@ impl Document { Language::Tex => { let green = parser::parse_latex(&text, ¶ms.config.syntax); let mut semantics = semantics::tex::Semantics::default(); - semantics.process_root(&latex::SyntaxNode::new_root(green.clone())); + semantics.process_root( + ¶ms.config.syntax, + &latex::SyntaxNode::new_root(green.clone()), + ); DocumentData::Tex(TexDocumentData { green, semantics }) } Language::Bib => { diff --git a/crates/base-db/src/semantics/tex.rs b/crates/base-db/src/semantics/tex.rs index 7e4c926f..c03f7d8f 100644 --- a/crates/base-db/src/semantics/tex.rs +++ b/crates/base-db/src/semantics/tex.rs @@ -1,9 +1,26 @@ use rowan::{ast::AstNode, TextRange}; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use syntax::latex::{self, HasBrack, HasCurly}; use titlecase::titlecase; +use parser::SyntaxConfig; + use super::Span; +use crate::semantics::tex::latex::SyntaxToken; + +fn maybe_prepend_prefix( + map: &FxHashMap, + command: &Option, + name: &Span, +) -> Span { + match command { + Some(x) => Span::new( + map.get(&x.text()[1..]).unwrap_or(&String::new()).to_owned() + &name.text, + name.range, + ), + None => name.clone(), + } +} #[derive(Debug, Clone, Default)] pub struct Semantics { @@ -19,11 +36,11 @@ pub struct Semantics { } impl Semantics { - pub fn process_root(&mut self, root: &latex::SyntaxNode) { + pub fn process_root(&mut self, conf: &SyntaxConfig, root: &latex::SyntaxNode) { for node in root.descendants_with_tokens() { match node { latex::SyntaxElement::Node(node) => { - self.process_node(&node); + self.process_node(conf, &node); } latex::SyntaxElement::Token(token) => { if token.kind() == latex::COMMAND_NAME { @@ -40,17 +57,17 @@ impl Semantics { .any(|link| link.kind == LinkKind::Cls && link.path.text == "subfiles"); } - fn process_node(&mut self, node: &latex::SyntaxNode) { + fn process_node(&mut self, conf: &SyntaxConfig, node: &latex::SyntaxNode) { if let Some(include) = latex::Include::cast(node.clone()) { self.process_include(include); } else if let Some(import) = latex::Import::cast(node.clone()) { self.process_import(import); } else if let Some(label) = latex::LabelDefinition::cast(node.clone()) { - self.process_label_definition(label); + self.process_label_definition(conf, label); } else if let Some(label) = latex::LabelReference::cast(node.clone()) { - self.process_label_reference(label); + self.process_label_reference(conf, label); } else if let Some(label) = latex::LabelReferenceRange::cast(node.clone()) { - self.process_label_reference_range(label); + self.process_label_reference_range(conf, label); } else if let Some(citation) = latex::Citation::cast(node.clone()) { self.process_citation(citation); } else if let Some(environment) = latex::Environment::cast(node.clone()) { @@ -111,7 +128,7 @@ impl Semantics { }); } - fn process_label_definition(&mut self, label: latex::LabelDefinition) { + fn process_label_definition(&mut self, conf: &SyntaxConfig, label: latex::LabelDefinition) { let Some(name) = label.name().and_then(|group| group.key()) else { return; }; @@ -190,13 +207,13 @@ impl Semantics { self.labels.push(Label { kind: LabelKind::Definition, - name, + name: maybe_prepend_prefix(&conf.label_definition_prefixes, &label.command(), &name), targets: objects, full_range, }); } - fn process_label_reference(&mut self, label: latex::LabelReference) { + fn process_label_reference(&mut self, conf: &SyntaxConfig, label: latex::LabelReference) { let Some(name_list) = label.name_list() else { return; }; @@ -207,7 +224,11 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::Reference, - name, + name: maybe_prepend_prefix( + &conf.label_reference_prefixes, + &label.command(), + &name, + ), targets: Vec::new(), full_range, }); @@ -215,14 +236,22 @@ impl Semantics { } } - fn process_label_reference_range(&mut self, label: latex::LabelReferenceRange) { + fn process_label_reference_range( + &mut self, + conf: &SyntaxConfig, + label: latex::LabelReferenceRange, + ) { let full_range = latex::small_range(&label); if let Some(from) = label.from().and_then(|group| group.key()) { let name = Span::from(&from); if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::ReferenceRange, - name, + name: maybe_prepend_prefix( + &conf.label_reference_prefixes, + &label.command(), + &name, + ), targets: Vec::new(), full_range, }); @@ -234,7 +263,11 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::ReferenceRange, - name, + name: maybe_prepend_prefix( + &conf.label_reference_prefixes, + &label.command(), + &name, + ), targets: Vec::new(), full_range, }); diff --git a/crates/diagnostics/src/labels.rs b/crates/diagnostics/src/labels.rs index f7e07211..15b5aaad 100644 --- a/crates/diagnostics/src/labels.rs +++ b/crates/diagnostics/src/labels.rs @@ -13,8 +13,6 @@ pub fn detect_undefined_and_unused_labels( workspace: &Workspace, results: &mut FxHashMap>, ) { - let def_prefs = &workspace.config().syntax.label_definition_prefixes; - let ref_prefs = &workspace.config().syntax.label_reference_prefixes; for document in workspace.iter() { let DocumentData::Tex(data) = &document.data else { continue; From 1f2eb76c8fb0b268d57a7ea9769532210fc20daa Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Mon, 10 Jun 2024 15:12:43 +0200 Subject: [PATCH 03/21] Add tests for custom labels (+prefixes) defs&refs. Adds two tests that ensure completion for custom labels with custom prefix works as expected. The `\ref` command should list the prefix, while a custom reference command with a configured prefix should not. --- Cargo.lock | 1 + crates/completion/Cargo.toml | 1 + crates/completion/src/tests.rs | 117 ++++++++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 140bd097..e86f35fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,6 +334,7 @@ dependencies = [ "expect-test", "fuzzy-matcher", "line-index", + "parser", "rayon", "rowan", "rustc-hash", diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml index e28e04df..6469b5f0 100644 --- a/crates/completion/Cargo.toml +++ b/crates/completion/Cargo.toml @@ -22,6 +22,7 @@ criterion = "0.5.1" distro = { path = "../distro" } expect-test = "1.5.0" test-utils = { path = "../test-utils" } +parser = { path = "../parser" } [lib] doctest = false diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index c6bed6e0..c44716b7 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -1,11 +1,18 @@ -use base_db::FeatureParams; +use base_db::{Config, FeatureParams}; use expect_test::{expect, Expect}; +use parser::SyntaxConfig; use rowan::TextRange; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::CompletionParams; -fn check(input: &str, expect: Expect) { - let fixture = test_utils::fixture::Fixture::parse(input); +fn check_with_syntax_config(config: SyntaxConfig, input: &str, expect: Expect) { + let mut fixture = test_utils::fixture::Fixture::parse(input); + fixture.workspace.set_config(Config { + syntax: config, + ..Config::default() + }); + let fixture = fixture; let (offset, spec) = fixture .documents @@ -37,6 +44,10 @@ fn check(input: &str, expect: Expect) { expect.assert_debug_eq(&items); } +fn check(input: &str, expect: Expect) { + check_with_syntax_config(SyntaxConfig::default(), input, expect) +} + #[test] fn acronym_ref_simple() { check( @@ -2134,3 +2145,103 @@ fn issue_885() { "#]], ); } + +#[test] +fn test_custom_label_prefix_ref() { + let mut config = SyntaxConfig::default(); + config.label_definition_commands.insert("asm".to_string()); + config.label_reference_commands.insert("asmref".to_string()); + config + .label_reference_prefixes + .insert("asm".to_string(), "asm:".to_string()); + + check_with_syntax_config( + config, + r#" +%! main.tex +\documentclass{article} +\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} +\newcommand{\asmref}[1]{\ref{asm:#1}} +\begin{document} + \begin{enumerate}\label{baz} + \asm{foo}{what} + \end{enumerate} + + \ref{} + | +\end{document} +% Comment"#, + expect![[r#" + [ + Label( + LabelData { + name: "baz", + header: None, + footer: None, + object: None, + keywords: "baz", + }, + ), + Label( + LabelData { + name: "asm:foo", + header: None, + footer: None, + object: None, + keywords: "asm:foo", + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_custom_label_prefix_custom_ref() { + let mut config = SyntaxConfig::default(); + config.label_definition_commands.insert("asm".to_string()); + config.label_reference_commands.insert("asmref".to_string()); + config + .label_reference_prefixes + .insert("asm".to_string(), "asm:".to_string()); + + check_with_syntax_config( + config, + r#" +%! main.tex +\documentclass{article} +\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} +\newcommand{\asmref}[1]{\ref{asm:#1}} +\begin{document} + \begin{enumerate}\label{baz} + \asm{foo}{what} + \end{enumerate} + + \asmref{} + | +\end{document} +% Comment"#, + expect![[r#" + [ + Label( + LabelData { + name: "baz", + header: None, + footer: None, + object: None, + keywords: "baz", + }, + ), + Label( + LabelData { + name: "foo", + header: None, + footer: None, + object: None, + keywords: "foo", + }, + ), + ] + "#]], + ); +} From daaaee0d8e7ccfa30d99071fc7dec5c01159b0c3 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Mon, 10 Jun 2024 15:29:45 +0200 Subject: [PATCH 04/21] Remove uneccessary imports. --- crates/completion/src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index c44716b7..f532cdfb 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -2,7 +2,6 @@ use base_db::{Config, FeatureParams}; use expect_test::{expect, Expect}; use parser::SyntaxConfig; use rowan::TextRange; -use rustc_hash::{FxHashMap, FxHashSet}; use crate::CompletionParams; From d2626eb07eec579648903af157cefdd2a5aea027 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Mon, 10 Jun 2024 16:55:41 +0200 Subject: [PATCH 05/21] Make `LabelData::name` a `String` to omit/add prefixes. --- crates/completion/src/lib.rs | 4 +- crates/completion/src/providers/label_def.rs | 2 +- crates/completion/src/providers/label_ref.rs | 44 ++++++++++++++++---- crates/texlab/src/features/completion.rs | 2 +- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index b1050052..234c0708 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -72,7 +72,7 @@ impl<'a> CompletionItemData<'a> { Self::Citation(data) => &data.entry.name.text, Self::Environment(data) => data.name, Self::GlossaryEntry(data) => &data.name, - Self::Label(data) => data.name, + Self::Label(data) => &data.name, Self::Color(name) => name, Self::ColorModel(name) => name, Self::File(name) => name, @@ -165,7 +165,7 @@ pub struct GlossaryEntryData { #[derive(Debug, PartialEq, Eq)] pub struct LabelData<'a> { - pub name: &'a str, + pub name: String, pub header: Option, pub footer: Option<&'a str>, pub object: Option>, diff --git a/crates/completion/src/providers/label_def.rs b/crates/completion/src/providers/label_def.rs index a8966ed6..943a9dac 100644 --- a/crates/completion/src/providers/label_def.rs +++ b/crates/completion/src/providers/label_def.rs @@ -36,7 +36,7 @@ pub fn complete_label_definitions<'a>( }; let data = crate::LabelData { - name: label, + name: label.to_string(), header: None, footer: None, object: None, diff --git a/crates/completion/src/providers/label_ref.rs b/crates/completion/src/providers/label_ref.rs index 6f71ae32..ea575758 100644 --- a/crates/completion/src/providers/label_ref.rs +++ b/crates/completion/src/providers/label_ref.rs @@ -11,12 +11,31 @@ use crate::{ CompletionItem, CompletionItemData, CompletionParams, }; +fn trim_prefix(prefix: Option<&String>, text: &String) -> String { + if let Some(pref) = prefix { + if text.starts_with(pref) { + return text[pref.len()..].to_string(); + } + } + text.clone() +} + pub fn complete_label_references<'a>( params: &'a CompletionParams<'a>, builder: &mut CompletionBuilder<'a>, ) -> Option<()> { - let FindResult { cursor, is_math } = - find_reference(params).or_else(|| find_reference_range(params))?; + let FindResult { + cursor, + is_math, + command, + } = find_reference(params).or_else(|| find_reference_range(params))?; + let ref_pref = params + .feature + .workspace + .config() + .syntax + .label_reference_prefixes + .get(&command); for document in ¶ms.feature.project.documents { let DocumentData::Tex(data) = &document.data else { @@ -29,6 +48,7 @@ pub fn complete_label_references<'a>( .iter() .filter(|label| label.kind == LabelKind::Definition) { + let labeltext = trim_prefix(ref_pref, &label.name.text); match render_label(params.feature.workspace, ¶ms.feature.project, label) { Some(rendered_label) => { if is_math && !matches!(rendered_label.object, RenderedObject::Equation) { @@ -41,11 +61,12 @@ pub fn complete_label_references<'a>( _ => None, }; - let keywords = format!("{} {}", label.name.text, rendered_label.reference()); + let keywords = format!("{} {}", labeltext, rendered_label.reference()); if let Some(score) = builder.matcher.score(&keywords, &cursor.text) { + let name = trim_prefix(ref_pref, &label.name.text); let data = CompletionItemData::Label(crate::LabelData { - name: &label.name.text, + name, header, footer, object: Some(rendered_label.object), @@ -59,12 +80,13 @@ pub fn complete_label_references<'a>( } None => { if let Some(score) = builder.matcher.score(&label.name.text, &cursor.text) { + let name = trim_prefix(ref_pref, &label.name.text); let data = CompletionItemData::Label(crate::LabelData { - name: &label.name.text, + name, header: None, footer: None, object: None, - keywords: label.name.text.clone(), + keywords: labeltext, }); builder @@ -82,20 +104,26 @@ pub fn complete_label_references<'a>( struct FindResult { cursor: Span, is_math: bool, + command: String, } fn find_reference(params: &CompletionParams) -> Option { let (cursor, group) = find_curly_group_word_list(params)?; let reference = latex::LabelReference::cast(group.syntax().parent()?)?; let is_math = reference.command()?.text() == "\\eqref"; - Some(FindResult { cursor, is_math }) + Some(FindResult { + cursor, + is_math, + command: reference.command()?.text()[1..].to_string(), + }) } fn find_reference_range(params: &CompletionParams) -> Option { let (cursor, group) = find_curly_group_word(params)?; - latex::LabelReferenceRange::cast(group.syntax().parent()?)?; + let refrange = latex::LabelReferenceRange::cast(group.syntax().parent()?)?; Some(FindResult { cursor, is_math: false, + command: refrange.command()?.text()[1..].to_string(), }) } diff --git a/crates/texlab/src/features/completion.rs b/crates/texlab/src/features/completion.rs index 09326f8e..b0993cff 100644 --- a/crates/texlab/src/features/completion.rs +++ b/crates/texlab/src/features/completion.rs @@ -296,7 +296,7 @@ impl<'a> ItemBuilder<'a> { None => Structure::Label, }; - result.label = data.name.into(); + result.label = data.name.clone().into(); result.kind = Some(structure.completion_kind()); result.detail = data.header; result.filter_text = Some(data.keywords); From c1f81af232aab4416df744158f5659e75523900d Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Mon, 10 Jun 2024 23:42:07 +0200 Subject: [PATCH 06/21] Add definition_prefixes to test. --- crates/completion/src/tests.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index f532cdfb..0c66181a 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -2150,6 +2150,9 @@ fn test_custom_label_prefix_ref() { let mut config = SyntaxConfig::default(); config.label_definition_commands.insert("asm".to_string()); config.label_reference_commands.insert("asmref".to_string()); + config + .label_definition_prefixes + .insert("asm".to_string(), "asm:".to_string()); config .label_reference_prefixes .insert("asm".to_string(), "asm:".to_string()); @@ -2174,20 +2177,20 @@ fn test_custom_label_prefix_ref() { [ Label( LabelData { - name: "baz", + name: "asm:foo", header: None, footer: None, object: None, - keywords: "baz", + keywords: "asm:foo", }, ), Label( LabelData { - name: "asm:foo", + name: "baz", header: None, footer: None, object: None, - keywords: "asm:foo", + keywords: "baz", }, ), ] From b4cfa78bb29c84644fdce8b8ea2e7b300b9a0bda Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Tue, 11 Jun 2024 00:26:30 +0200 Subject: [PATCH 07/21] Update completion tests for custom label prefixes. The suggestions should only contain those that are starting with a prefix, if such a prefix has been defined. --- crates/completion/src/tests.rs | 123 ++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index 0c66181a..d84bb988 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -2227,13 +2227,59 @@ fn test_custom_label_prefix_custom_ref() { [ Label( LabelData { - name: "baz", + name: "foo", header: None, footer: None, object: None, - keywords: "baz", + keywords: "foo", }, ), + ] + "#]], + ); +} + +#[test] +fn test_custom_label_multiple_prefix_custom_ref() { + let mut config = SyntaxConfig::default(); + config + .label_definition_commands + .extend(vec!["asm", "goal"].into_iter().map(String::from)); + config + .label_reference_commands + .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); + config.label_definition_prefixes.extend( + vec![("asm", "asm:"), ("goal", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + config.label_reference_prefixes.extend( + vec![("asm", "asm:"), ("goal", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + + check_with_syntax_config( + config, + r#" +%! main.tex +\documentclass{article} +\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} +\newcommand{\asmref}[1]{\ref{asm:#1}} +\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {#2}} +\newcommand{\goalref}[1]{\ref{goal:#1}} +\begin{document} + \begin{enumerate}\label{baz} + \asm{foo}{what} + \goal{foo}{what} + \end{enumerate} + + \goalref{} + | +\end{document} +% Comment"#, + expect![[r#" + [ Label( LabelData { name: "foo", @@ -2247,3 +2293,76 @@ fn test_custom_label_prefix_custom_ref() { "#]], ); } + +#[test] +fn test_custom_label_multiple_prefix_ref() { + let mut config = SyntaxConfig::default(); + config + .label_definition_commands + .extend(vec!["asm", "goal"].into_iter().map(String::from)); + config + .label_reference_commands + .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); + config.label_definition_prefixes.extend( + vec![("asm", "asm:"), ("goal", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + config.label_reference_prefixes.extend( + vec![("asm", "asm:"), ("goal", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + + check_with_syntax_config( + config, + r#" +%! main.tex +\documentclass{article} +\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} +\newcommand{\asmref}[1]{\ref{asm:#1}} +\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {#2}} +\newcommand{\goalref}[1]{\ref{goal:#1}} +\begin{document} + \begin{enumerate}\label{baz} + \asm{foo}{what} + \goal{foo}{what} + \end{enumerate} + + \ref{} + | +\end{document} +% Comment"#, + expect![[r#" + [ + Label( + LabelData { + name: "asm:foo", + header: None, + footer: None, + object: None, + keywords: "asm:foo", + }, + ), + Label( + LabelData { + name: "baz", + header: None, + footer: None, + object: None, + keywords: "baz", + }, + ), + Label( + LabelData { + name: "goal:foo", + header: None, + footer: None, + object: None, + keywords: "goal:foo", + }, + ), + ] + "#]], + ); +} From 1798e8ffa2caed667d61abefc7097835ae6f535f Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Tue, 11 Jun 2024 00:33:37 +0200 Subject: [PATCH 08/21] Typo fix. --- crates/completion/src/tests.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index d84bb988..67aa7363 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -2245,16 +2245,16 @@ fn test_custom_label_multiple_prefix_custom_ref() { config .label_definition_commands .extend(vec!["asm", "goal"].into_iter().map(String::from)); - config - .label_reference_commands - .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); config.label_definition_prefixes.extend( vec![("asm", "asm:"), ("goal", "goal:")] .into_iter() .map(|(x, y)| (String::from(x), String::from(y))), ); + config + .label_reference_commands + .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); config.label_reference_prefixes.extend( - vec![("asm", "asm:"), ("goal", "goal:")] + vec![("asmref", "asm:"), ("goalref", "goal:")] .into_iter() .map(|(x, y)| (String::from(x), String::from(y))), ); @@ -2300,16 +2300,16 @@ fn test_custom_label_multiple_prefix_ref() { config .label_definition_commands .extend(vec!["asm", "goal"].into_iter().map(String::from)); - config - .label_reference_commands - .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); config.label_definition_prefixes.extend( vec![("asm", "asm:"), ("goal", "goal:")] .into_iter() .map(|(x, y)| (String::from(x), String::from(y))), ); + config + .label_reference_commands + .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); config.label_reference_prefixes.extend( - vec![("asm", "asm:"), ("goal", "goal:")] + vec![("asmref", "asm:"), ("goalref", "goal:")] .into_iter() .map(|(x, y)| (String::from(x), String::from(y))), ); From 09ae9565a226b441064c5c58e57770c6e5dfab15 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Tue, 11 Jun 2024 00:36:14 +0200 Subject: [PATCH 09/21] Typo fix. --- crates/completion/src/tests.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index 67aa7363..6497051e 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -2149,13 +2149,9 @@ fn issue_885() { fn test_custom_label_prefix_ref() { let mut config = SyntaxConfig::default(); config.label_definition_commands.insert("asm".to_string()); - config.label_reference_commands.insert("asmref".to_string()); config .label_definition_prefixes .insert("asm".to_string(), "asm:".to_string()); - config - .label_reference_prefixes - .insert("asm".to_string(), "asm:".to_string()); check_with_syntax_config( config, @@ -2202,10 +2198,13 @@ fn test_custom_label_prefix_ref() { fn test_custom_label_prefix_custom_ref() { let mut config = SyntaxConfig::default(); config.label_definition_commands.insert("asm".to_string()); + config + .label_definition_prefixes + .insert("asm".to_string(), "asm:".to_string()); config.label_reference_commands.insert("asmref".to_string()); config .label_reference_prefixes - .insert("asm".to_string(), "asm:".to_string()); + .insert("asmref".to_string(), "asm:".to_string()); check_with_syntax_config( config, From beecde14ac24d0670cd74ee8de4b27de7289ab61 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Tue, 11 Jun 2024 00:45:28 +0200 Subject: [PATCH 10/21] Ignore labels without matching prefix. Suggestions for completion can be filtered already by checking the prefixes of known labels and wether they match the custom reference prefix of the respective reference command. --- crates/completion/src/providers/label_ref.rs | 7 ++++++ crates/completion/src/tests.rs | 24 -------------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/crates/completion/src/providers/label_ref.rs b/crates/completion/src/providers/label_ref.rs index ea575758..81333dd6 100644 --- a/crates/completion/src/providers/label_ref.rs +++ b/crates/completion/src/providers/label_ref.rs @@ -48,6 +48,13 @@ pub fn complete_label_references<'a>( .iter() .filter(|label| label.kind == LabelKind::Definition) { + if ref_pref.map_or(false, |pref| !label.name.text.starts_with(pref)) { + continue; + } + eprintln!( + "ref_pref: {:?} label.name.text: {:?}", + ref_pref, label.name.text + ); let labeltext = trim_prefix(ref_pref, &label.name.text); match render_label(params.feature.workspace, ¶ms.feature.project, label) { Some(rendered_label) => { diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index 6497051e..cd1bce6e 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -2157,17 +2157,12 @@ fn test_custom_label_prefix_ref() { config, r#" %! main.tex -\documentclass{article} -\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} -\newcommand{\asmref}[1]{\ref{asm:#1}} -\begin{document} \begin{enumerate}\label{baz} \asm{foo}{what} \end{enumerate} \ref{} | -\end{document} % Comment"#, expect![[r#" [ @@ -2210,17 +2205,12 @@ fn test_custom_label_prefix_custom_ref() { config, r#" %! main.tex -\documentclass{article} -\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} -\newcommand{\asmref}[1]{\ref{asm:#1}} -\begin{document} \begin{enumerate}\label{baz} \asm{foo}{what} \end{enumerate} \asmref{} | -\end{document} % Comment"#, expect![[r#" [ @@ -2262,12 +2252,6 @@ fn test_custom_label_multiple_prefix_custom_ref() { config, r#" %! main.tex -\documentclass{article} -\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} -\newcommand{\asmref}[1]{\ref{asm:#1}} -\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {#2}} -\newcommand{\goalref}[1]{\ref{goal:#1}} -\begin{document} \begin{enumerate}\label{baz} \asm{foo}{what} \goal{foo}{what} @@ -2275,7 +2259,6 @@ fn test_custom_label_multiple_prefix_custom_ref() { \goalref{} | -\end{document} % Comment"#, expect![[r#" [ @@ -2317,12 +2300,6 @@ fn test_custom_label_multiple_prefix_ref() { config, r#" %! main.tex -\documentclass{article} -\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {#2}} -\newcommand{\asmref}[1]{\ref{asm:#1}} -\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {#2}} -\newcommand{\goalref}[1]{\ref{goal:#1}} -\begin{document} \begin{enumerate}\label{baz} \asm{foo}{what} \goal{foo}{what} @@ -2330,7 +2307,6 @@ fn test_custom_label_multiple_prefix_ref() { \ref{} | -\end{document} % Comment"#, expect![[r#" [ From 402503cb78243b1f14c7deea59ccb7375faf9474 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Tue, 11 Jun 2024 11:36:34 +0200 Subject: [PATCH 11/21] Remove debug print. --- crates/completion/src/providers/label_ref.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/completion/src/providers/label_ref.rs b/crates/completion/src/providers/label_ref.rs index 81333dd6..54a2293a 100644 --- a/crates/completion/src/providers/label_ref.rs +++ b/crates/completion/src/providers/label_ref.rs @@ -51,10 +51,6 @@ pub fn complete_label_references<'a>( if ref_pref.map_or(false, |pref| !label.name.text.starts_with(pref)) { continue; } - eprintln!( - "ref_pref: {:?} label.name.text: {:?}", - ref_pref, label.name.text - ); let labeltext = trim_prefix(ref_pref, &label.name.text); match render_label(params.feature.workspace, ¶ms.feature.project, label) { Some(rendered_label) => { From e10d6976c6160dcf9a139ff10bfb0f9cb3b9509e Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Tue, 11 Jun 2024 20:48:40 +0200 Subject: [PATCH 12/21] Use `strip_prefix` and rollback type of name field in `LabelData`. --- crates/completion/src/lib.rs | 2 +- crates/completion/src/providers/label_def.rs | 2 +- crates/completion/src/providers/label_ref.rs | 16 +++++++--------- crates/texlab/src/features/completion.rs | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 234c0708..5bfd94d9 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -165,7 +165,7 @@ pub struct GlossaryEntryData { #[derive(Debug, PartialEq, Eq)] pub struct LabelData<'a> { - pub name: String, + pub name: &'a str, pub header: Option, pub footer: Option<&'a str>, pub object: Option>, diff --git a/crates/completion/src/providers/label_def.rs b/crates/completion/src/providers/label_def.rs index 943a9dac..a8966ed6 100644 --- a/crates/completion/src/providers/label_def.rs +++ b/crates/completion/src/providers/label_def.rs @@ -36,7 +36,7 @@ pub fn complete_label_definitions<'a>( }; let data = crate::LabelData { - name: label.to_string(), + name: label, header: None, footer: None, object: None, diff --git a/crates/completion/src/providers/label_ref.rs b/crates/completion/src/providers/label_ref.rs index 54a2293a..43448f0e 100644 --- a/crates/completion/src/providers/label_ref.rs +++ b/crates/completion/src/providers/label_ref.rs @@ -11,13 +11,10 @@ use crate::{ CompletionItem, CompletionItemData, CompletionParams, }; -fn trim_prefix(prefix: Option<&String>, text: &String) -> String { - if let Some(pref) = prefix { - if text.starts_with(pref) { - return text[pref.len()..].to_string(); - } - } - text.clone() +fn trim_prefix<'a>(prefix: Option<&'a str>, text: &'a str) -> &'a str { + prefix + .and_then(|pref| text.strip_prefix(pref)) + .unwrap_or(text) } pub fn complete_label_references<'a>( @@ -35,7 +32,8 @@ pub fn complete_label_references<'a>( .config() .syntax .label_reference_prefixes - .get(&command); + .get(&command) + .map(|x| x.as_str()); for document in ¶ms.feature.project.documents { let DocumentData::Tex(data) = &document.data else { @@ -89,7 +87,7 @@ pub fn complete_label_references<'a>( header: None, footer: None, object: None, - keywords: labeltext, + keywords: labeltext.to_string(), }); builder diff --git a/crates/texlab/src/features/completion.rs b/crates/texlab/src/features/completion.rs index b0993cff..09326f8e 100644 --- a/crates/texlab/src/features/completion.rs +++ b/crates/texlab/src/features/completion.rs @@ -296,7 +296,7 @@ impl<'a> ItemBuilder<'a> { None => Structure::Label, }; - result.label = data.name.clone().into(); + result.label = data.name.into(); result.kind = Some(structure.completion_kind()); result.detail = data.header; result.filter_text = Some(data.keywords); From 7243f37678e58ad46ffb1504edc482064cf7edce Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Tue, 11 Jun 2024 20:52:29 +0200 Subject: [PATCH 13/21] Use `FxHashMap` instead of a `Vec`. --- crates/texlab/src/server/options.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/texlab/src/server/options.rs b/crates/texlab/src/server/options.rs index 1640c9cf..3304c11f 100644 --- a/crates/texlab/src/server/options.rs +++ b/crates/texlab/src/server/options.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use rustc_hash::FxHashMap; + use regex::Regex; #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -126,9 +128,9 @@ pub struct ExperimentalOptions { pub verbatim_environments: Vec, pub citation_commands: Vec, pub label_definition_commands: Vec, - pub label_definition_prefixes: Vec<(String, String)>, + pub label_definition_prefixes: FxHashMap, pub label_reference_commands: Vec, - pub label_reference_prefixes: Vec<(String, String)>, + pub label_reference_prefixes: FxHashMap, } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] From d5d3ad599ee2c596947ad84546e0de6c070ca7e3 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 11:40:16 +0200 Subject: [PATCH 14/21] Store command in `Label`. --- crates/base-db/src/semantics/tex.rs | 13 +++++++++++++ crates/rename/src/label.rs | 1 + 2 files changed, 14 insertions(+) diff --git a/crates/base-db/src/semantics/tex.rs b/crates/base-db/src/semantics/tex.rs index c03f7d8f..5505afa9 100644 --- a/crates/base-db/src/semantics/tex.rs +++ b/crates/base-db/src/semantics/tex.rs @@ -207,6 +207,9 @@ impl Semantics { self.labels.push(Label { kind: LabelKind::Definition, + cmd: label + .command() + .map(|x| Span::new(x.text().to_string(), x.text_range())), name: maybe_prepend_prefix(&conf.label_definition_prefixes, &label.command(), &name), targets: objects, full_range, @@ -224,6 +227,9 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::Reference, + cmd: label + .command() + .map(|x| Span::new(x.text().to_string(), x.text_range())), name: maybe_prepend_prefix( &conf.label_reference_prefixes, &label.command(), @@ -247,6 +253,9 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::ReferenceRange, + cmd: label + .command() + .map(|x| Span::new(x.text().to_string(), x.text_range())), name: maybe_prepend_prefix( &conf.label_reference_prefixes, &label.command(), @@ -263,6 +272,9 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::ReferenceRange, + cmd: label + .command() + .map(|x| Span::new(x.text().to_string(), x.text_range())), name: maybe_prepend_prefix( &conf.label_reference_prefixes, &label.command(), @@ -369,6 +381,7 @@ pub enum LabelKind { #[derive(Debug, Clone)] pub struct Label { pub kind: LabelKind, + pub cmd: Option, pub name: Span, pub targets: Vec, pub full_range: TextRange, diff --git a/crates/rename/src/label.rs b/crates/rename/src/label.rs index b21e1b79..32cd30c4 100644 --- a/crates/rename/src/label.rs +++ b/crates/rename/src/label.rs @@ -17,6 +17,7 @@ pub(super) fn rename(builder: &mut RenameBuilder) -> Option<()> { let project = &builder.params.feature.project; for (document, label) in queries::objects_with_name::(project, &name.text) { + eprintln!("want to rename {:?} into {:?}", label, name); let entry = builder.result.changes.entry(document); entry.or_default().push(label.name_range()); } From 3e3b49685ff0a086a5e316c24b43eabad4aa78e9 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 13:29:22 +0200 Subject: [PATCH 15/21] Generalize rename tests to be parametric on `SyntaxConfig`s. --- Cargo.lock | 1 + crates/rename/Cargo.toml | 1 + crates/rename/src/tests.rs | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e86f35fc..b677bd40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1338,6 +1338,7 @@ name = "rename" version = "0.0.0" dependencies = [ "base-db", + "parser", "rowan", "rustc-hash", "syntax", diff --git a/crates/rename/Cargo.toml b/crates/rename/Cargo.toml index 1b2b91ee..dc755851 100644 --- a/crates/rename/Cargo.toml +++ b/crates/rename/Cargo.toml @@ -14,6 +14,7 @@ syntax = { path = "../syntax" } [dev-dependencies] test-utils = { path = "../test-utils" } +parser = { path = "../parser" } [lib] doctest = false diff --git a/crates/rename/src/tests.rs b/crates/rename/src/tests.rs index 0f426303..9abf2470 100644 --- a/crates/rename/src/tests.rs +++ b/crates/rename/src/tests.rs @@ -1,9 +1,17 @@ use rustc_hash::FxHashMap; +use base_db::{Config, FeatureParams}; +use parser::SyntaxConfig; + use crate::RenameParams; -fn check(input: &str) { - let fixture = test_utils::fixture::Fixture::parse(input); +fn check_with_syntax_config(config: SyntaxConfig, input: &str) { + let mut fixture = test_utils::fixture::Fixture::parse(input); + fixture.workspace.set_config(Config { + syntax: config, + ..Config::default() + }); + let fixture = fixture; let mut expected = FxHashMap::default(); for spec in &fixture.documents { @@ -12,12 +20,15 @@ fn check(input: &str) { expected.insert(document, spec.ranges.clone()); } } - let (feature, offset) = fixture.make_params().unwrap(); let actual = crate::rename(RenameParams { feature, offset }); assert_eq!(actual.changes, expected); } +fn check(input: &str) { + check_with_syntax_config(SyntaxConfig::default(), input) +} + #[test] fn test_command() { check( From 67d402c9e96c9154cb34377e606e50dee80ee343 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 13:33:56 +0200 Subject: [PATCH 16/21] Add rename tests. --- crates/rename/src/tests.rs | 84 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/crates/rename/src/tests.rs b/crates/rename/src/tests.rs index 9abf2470..3c3f8128 100644 --- a/crates/rename/src/tests.rs +++ b/crates/rename/src/tests.rs @@ -98,3 +98,87 @@ fn test_label() { "#, ) } + +#[test] +fn test_custom_label_ref() { + let mut config = SyntaxConfig::default(); + config + .label_definition_commands + .extend(vec!["asm", "goal"].into_iter().map(String::from)); + config.label_definition_prefixes.extend( + vec![("asm", "asm:"), ("goal", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + config + .label_reference_commands + .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); + config.label_reference_prefixes.extend( + vec![("asmref", "asm:"), ("goalref", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + check_with_syntax_config( + config, + r#" +%! foo.tex +\goal{foo} + +\asm{foo}\include{bar}\include{baz} + | + ^^^ + +%! bar.tex +\asmref{foo} + ^^^ + +%! baz.tex +\ref{foo} + +\ref{asm:foo} + ^^^^^^^ +"#, + ) +} + +#[test] +fn test_custom_label_def() { + let mut config = SyntaxConfig::default(); + config + .label_definition_commands + .extend(vec!["asm", "goal"].into_iter().map(String::from)); + config.label_definition_prefixes.extend( + vec![("asm", "asm:"), ("goal", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + config + .label_reference_commands + .extend(vec!["asmref", "goalref"].into_iter().map(String::from)); + config.label_reference_prefixes.extend( + vec![("asmref", "asm:"), ("goalref", "goal:")] + .into_iter() + .map(|(x, y)| (String::from(x), String::from(y))), + ); + check_with_syntax_config( + config, + r#" +%! foo.tex +\goal{foo} + +\label{asm:foo}\include{bar}\include{baz} + | + ^^^^^^^ + +%! bar.tex +\asmref{foo} + ^^^ + +%! baz.tex +\ref{foo} + +\ref{asm:foo} + ^^^^^^^ +"#, + ) +} From 0d065c9ce91f918d3419fff745ad5816c8a4be19 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 13:42:39 +0200 Subject: [PATCH 17/21] Consider prefixes when renaming. This commit implements key functionality for renaming labels with prefixes. A sane assumption it does is that all labels found as candidate for renaming share a common prefix, up to looking it up first and prepending it, e.g., for `\goal{foo}`. Instead of storing a renaming candidate for each entry, it only keeps track of the prefixes and prepends them accordingly, depending on the renaming candidate, by swapping the `TextRange` with `RenameInformation` inside the `RenameResult`. Unfortunately, this pollutes a bit the other renaming ops, such as commands or citations, which don't have prefixes. Nevertheless, changes there have been incremental and `RenameInformation` implements a `From` to easily map an existing `TextRange` into it, simply assuming an empty prefix. In terms of tests, the `prefix` should be ignored. --- crates/base-db/src/semantics/tex.rs | 18 ++++------- crates/rename/src/command.rs | 5 +++- crates/rename/src/entry.rs | 2 +- crates/rename/src/label.rs | 46 +++++++++++++++++++++++++++-- crates/rename/src/lib.rs | 22 +++++++++++++- crates/rename/src/tests.rs | 16 ++++++---- crates/texlab/src/util/to_proto.rs | 11 +++++-- 7 files changed, 93 insertions(+), 27 deletions(-) diff --git a/crates/base-db/src/semantics/tex.rs b/crates/base-db/src/semantics/tex.rs index 5505afa9..1b92dd1e 100644 --- a/crates/base-db/src/semantics/tex.rs +++ b/crates/base-db/src/semantics/tex.rs @@ -207,9 +207,7 @@ impl Semantics { self.labels.push(Label { kind: LabelKind::Definition, - cmd: label - .command() - .map(|x| Span::new(x.text().to_string(), x.text_range())), + cmd: label.command().map(|x| x.text()[1..].to_string()), name: maybe_prepend_prefix(&conf.label_definition_prefixes, &label.command(), &name), targets: objects, full_range, @@ -227,9 +225,7 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::Reference, - cmd: label - .command() - .map(|x| Span::new(x.text().to_string(), x.text_range())), + cmd: label.command().map(|x| x.text()[1..].to_string()), name: maybe_prepend_prefix( &conf.label_reference_prefixes, &label.command(), @@ -253,9 +249,7 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::ReferenceRange, - cmd: label - .command() - .map(|x| Span::new(x.text().to_string(), x.text_range())), + cmd: label.command().map(|x| x.text()[1..].to_string()), name: maybe_prepend_prefix( &conf.label_reference_prefixes, &label.command(), @@ -272,9 +266,7 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::ReferenceRange, - cmd: label - .command() - .map(|x| Span::new(x.text().to_string(), x.text_range())), + cmd: label.command().map(|x| x.text()[1..].to_string()), name: maybe_prepend_prefix( &conf.label_reference_prefixes, &label.command(), @@ -381,7 +373,7 @@ pub enum LabelKind { #[derive(Debug, Clone)] pub struct Label { pub kind: LabelKind, - pub cmd: Option, + pub cmd: Option, pub name: Span, pub targets: Vec, pub full_range: TextRange, diff --git a/crates/rename/src/command.rs b/crates/rename/src/command.rs index 7b17d427..037e09e4 100644 --- a/crates/rename/src/command.rs +++ b/crates/rename/src/command.rs @@ -35,7 +35,10 @@ pub(super) fn rename(builder: &mut RenameBuilder) -> Option<()> { } } - builder.result.changes.insert(*document, edits); + builder + .result + .changes + .insert(*document, edits.iter().map(|&x| x.into()).collect()); } Some(()) diff --git a/crates/rename/src/entry.rs b/crates/rename/src/entry.rs index 5bed4ae6..d35d9e9e 100644 --- a/crates/rename/src/entry.rs +++ b/crates/rename/src/entry.rs @@ -42,7 +42,7 @@ pub(super) fn rename(builder: &mut RenameBuilder) -> Option<()> { for (document, range) in citations.chain(entries) { let entry = builder.result.changes.entry(document); - entry.or_default().push(range); + entry.or_default().push(range.into()); } Some(()) diff --git a/crates/rename/src/label.rs b/crates/rename/src/label.rs index 32cd30c4..86f181b9 100644 --- a/crates/rename/src/label.rs +++ b/crates/rename/src/label.rs @@ -2,8 +2,9 @@ use base_db::{ semantics::{tex, Span}, util::queries::{self, Object}, }; +use rustc_hash::FxHashMap; -use crate::{RenameBuilder, RenameParams}; +use crate::{RenameBuilder, RenameInformation, RenameParams}; pub(super) fn prepare_rename(params: &RenameParams) -> Option { let data = params.feature.document.data.as_tex()?; @@ -12,14 +13,53 @@ pub(super) fn prepare_rename(params: &RenameParams) -> Option { Some(Span::new(label.object.name.text.clone(), label.range)) } +struct PrefixInformation<'a> { + def_prefixes: &'a FxHashMap, + ref_prefixes: &'a FxHashMap, +} + +fn label_has_prefix(pref_info: &PrefixInformation, label: &tex::Label) -> Option { + match label.kind { + tex::LabelKind::Definition => pref_info + .def_prefixes + .get(&label.cmd.clone().unwrap_or(String::new())) + .cloned(), + _ => pref_info + .ref_prefixes + .get(&label.cmd.clone().unwrap_or(String::new())) + .cloned(), + } +} + +fn find_prefix_in_any( + builder: &mut RenameBuilder, + pref_info: &PrefixInformation, + name: &str, +) -> Option { + let project = &builder.params.feature.project; + queries::objects_with_name::(project, name) + .find_map(|(_, label)| label_has_prefix(&pref_info, label)) +} + pub(super) fn rename(builder: &mut RenameBuilder) -> Option<()> { let name = prepare_rename(&builder.params)?; + let syn = &builder.params.feature.workspace.config().syntax; + let pref_info = PrefixInformation { + def_prefixes: &syn.label_definition_prefixes, + ref_prefixes: &syn.label_reference_prefixes, + }; + let prefix = find_prefix_in_any(builder, &pref_info, &name.text); + let project = &builder.params.feature.project; for (document, label) in queries::objects_with_name::(project, &name.text) { - eprintln!("want to rename {:?} into {:?}", label, name); + let prefix = label_has_prefix(&pref_info, label).map_or(prefix.clone(), |_| None); + let entry = builder.result.changes.entry(document); - entry.or_default().push(label.name_range()); + entry.or_default().push(RenameInformation { + range: label.name_range(), + prefix: prefix.clone(), + }); } Some(()) diff --git a/crates/rename/src/lib.rs b/crates/rename/src/lib.rs index bf9023dd..f66d4c31 100644 --- a/crates/rename/src/lib.rs +++ b/crates/rename/src/lib.rs @@ -12,9 +12,20 @@ pub struct RenameParams<'a> { pub offset: TextSize, } +#[derive(Debug, Default)] +pub struct RenameInformation { + pub range: TextRange, + pub prefix: Option, +} +impl PartialEq for RenameInformation { + fn eq(&self, other: &Self) -> bool { + self.range == other.range + } +} + #[derive(Debug, Default)] pub struct RenameResult<'a> { - pub changes: FxHashMap<&'a Document, Vec>, + pub changes: FxHashMap<&'a Document, Vec>, } struct RenameBuilder<'a> { @@ -22,6 +33,15 @@ struct RenameBuilder<'a> { result: RenameResult<'a>, } +impl From for RenameInformation { + fn from(range: TextRange) -> Self { + RenameInformation { + range, + prefix: None, + } + } +} + pub fn prepare_rename(params: &RenameParams) -> Option { command::prepare_rename(params) .or_else(|| entry::prepare_rename(params)) diff --git a/crates/rename/src/tests.rs b/crates/rename/src/tests.rs index 3c3f8128..cff6dc9e 100644 --- a/crates/rename/src/tests.rs +++ b/crates/rename/src/tests.rs @@ -3,7 +3,7 @@ use rustc_hash::FxHashMap; use base_db::{Config, FeatureParams}; use parser::SyntaxConfig; -use crate::RenameParams; +use crate::{RenameInformation, RenameParams}; fn check_with_syntax_config(config: SyntaxConfig, input: &str) { let mut fixture = test_utils::fixture::Fixture::parse(input); @@ -13,11 +13,14 @@ fn check_with_syntax_config(config: SyntaxConfig, input: &str) { }); let fixture = fixture; - let mut expected = FxHashMap::default(); + let mut expected: FxHashMap<_, Vec> = FxHashMap::default(); for spec in &fixture.documents { if !spec.ranges.is_empty() { let document = fixture.workspace.lookup(&spec.uri).unwrap(); - expected.insert(document, spec.ranges.clone()); + expected.insert( + document, + spec.ranges.iter().map(|r| r.clone().into()).collect(), + ); } } let (feature, offset) = fixture.make_params().unwrap(); @@ -89,12 +92,12 @@ fn test_label() { | ^^^ -%! bar.tex +%! baz.tex \ref{foo} - ^^^ -%! baz.tex +%! bar.tex \ref{foo} + ^^^ "#, ) } @@ -137,6 +140,7 @@ fn test_custom_label_ref() { \ref{asm:foo} ^^^^^^^ + "#, ) } diff --git a/crates/texlab/src/util/to_proto.rs b/crates/texlab/src/util/to_proto.rs index f5cf2edf..d2c261c8 100644 --- a/crates/texlab/src/util/to_proto.rs +++ b/crates/texlab/src/util/to_proto.rs @@ -396,8 +396,15 @@ pub fn workspace_edit(result: RenameResult, new_name: &str) -> lsp_types::Worksp let mut edits = Vec::new(); ranges .into_iter() - .filter_map(|range| document.line_index.line_col_lsp_range(range)) - .for_each(|range| edits.push(lsp_types::TextEdit::new(range, new_name.into()))); + .filter_map(|info| { + document.line_index.line_col_lsp_range(info.range).map(|i| { + ( + i, + info.prefix.map_or(new_name.into(), |p| p + new_name.into()), + ) + }) + }) + .for_each(|(range, new_name)| edits.push(lsp_types::TextEdit::new(range, new_name))); changes.insert(document.uri.clone(), edits); } From 780d173db5922349d2ee97345d48a10c47737678 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 13:48:56 +0200 Subject: [PATCH 18/21] Move `impl`. --- crates/rename/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/rename/src/lib.rs b/crates/rename/src/lib.rs index f66d4c31..e6c05ad2 100644 --- a/crates/rename/src/lib.rs +++ b/crates/rename/src/lib.rs @@ -17,11 +17,6 @@ pub struct RenameInformation { pub range: TextRange, pub prefix: Option, } -impl PartialEq for RenameInformation { - fn eq(&self, other: &Self) -> bool { - self.range == other.range - } -} #[derive(Debug, Default)] pub struct RenameResult<'a> { @@ -41,6 +36,11 @@ impl From for RenameInformation { } } } +impl PartialEq for RenameInformation { + fn eq(&self, other: &Self) -> bool { + self.range == other.range + } +} pub fn prepare_rename(params: &RenameParams) -> Option { command::prepare_rename(params) From 8624b2420f2c9209af86a5a98518fd01207c4e24 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 13:52:19 +0200 Subject: [PATCH 19/21] Remove redundant import. --- crates/rename/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rename/src/tests.rs b/crates/rename/src/tests.rs index cff6dc9e..4da866fb 100644 --- a/crates/rename/src/tests.rs +++ b/crates/rename/src/tests.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashMap; -use base_db::{Config, FeatureParams}; +use base_db::Config; use parser::SyntaxConfig; use crate::{RenameInformation, RenameParams}; From ad666de30e5db9b38ad9b6e45b3bfa755a2536d1 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 14:04:46 +0200 Subject: [PATCH 20/21] Move function down. --- crates/rename/src/label.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/rename/src/label.rs b/crates/rename/src/label.rs index 86f181b9..ca3fdac3 100644 --- a/crates/rename/src/label.rs +++ b/crates/rename/src/label.rs @@ -6,13 +6,6 @@ use rustc_hash::FxHashMap; use crate::{RenameBuilder, RenameInformation, RenameParams}; -pub(super) fn prepare_rename(params: &RenameParams) -> Option { - let data = params.feature.document.data.as_tex()?; - let labels = &data.semantics.labels; - let label = queries::object_at_cursor(labels, params.offset, queries::SearchMode::Name)?; - Some(Span::new(label.object.name.text.clone(), label.range)) -} - struct PrefixInformation<'a> { def_prefixes: &'a FxHashMap, ref_prefixes: &'a FxHashMap, @@ -41,6 +34,14 @@ fn find_prefix_in_any( .find_map(|(_, label)| label_has_prefix(&pref_info, label)) } +pub(super) fn prepare_rename(params: &RenameParams) -> Option { + let data = params.feature.document.data.as_tex()?; + let labels = &data.semantics.labels; + let label = queries::object_at_cursor(labels, params.offset, queries::SearchMode::Name)?; + + Some(Span::new(label.object.name.text.clone(), label.range)) +} + pub(super) fn rename(builder: &mut RenameBuilder) -> Option<()> { let name = prepare_rename(&builder.params)?; From 3b4d1d0f82a2a412ddf32a7d09bbfc7115309d84 Mon Sep 17 00:00:00 2001 From: Matthis Kruse Date: Thu, 13 Jun 2024 14:20:00 +0200 Subject: [PATCH 21/21] Use `Vec` instead of `FxHashMap`. --- crates/base-db/src/semantics/tex.rs | 10 ++++++--- crates/completion/src/providers/label_ref.rs | 3 ++- crates/completion/src/tests.rs | 6 ++--- crates/parser/src/config.rs | 6 ++--- crates/rename/src/label.rs | 23 ++++++++++---------- crates/texlab/src/server/options.rs | 6 ++--- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/crates/base-db/src/semantics/tex.rs b/crates/base-db/src/semantics/tex.rs index 1b92dd1e..a1301091 100644 --- a/crates/base-db/src/semantics/tex.rs +++ b/crates/base-db/src/semantics/tex.rs @@ -1,5 +1,5 @@ use rowan::{ast::AstNode, TextRange}; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use syntax::latex::{self, HasBrack, HasCurly}; use titlecase::titlecase; @@ -9,13 +9,17 @@ use super::Span; use crate::semantics::tex::latex::SyntaxToken; fn maybe_prepend_prefix( - map: &FxHashMap, + map: &Vec<(String, String)>, command: &Option, name: &Span, ) -> Span { match command { Some(x) => Span::new( - map.get(&x.text()[1..]).unwrap_or(&String::new()).to_owned() + &name.text, + map.iter() + .find_map(|(k, v)| if k == &x.text()[1..] { Some(v) } else { None }) + .unwrap_or(&String::new()) + .to_owned() + + &name.text, name.range, ), None => name.clone(), diff --git a/crates/completion/src/providers/label_ref.rs b/crates/completion/src/providers/label_ref.rs index 43448f0e..8b262f13 100644 --- a/crates/completion/src/providers/label_ref.rs +++ b/crates/completion/src/providers/label_ref.rs @@ -32,7 +32,8 @@ pub fn complete_label_references<'a>( .config() .syntax .label_reference_prefixes - .get(&command) + .iter() + .find_map(|(k, v)| if *k == command { Some(v) } else { None }) .map(|x| x.as_str()); for document in ¶ms.feature.project.documents { diff --git a/crates/completion/src/tests.rs b/crates/completion/src/tests.rs index cd1bce6e..8c68b71d 100644 --- a/crates/completion/src/tests.rs +++ b/crates/completion/src/tests.rs @@ -2151,7 +2151,7 @@ fn test_custom_label_prefix_ref() { config.label_definition_commands.insert("asm".to_string()); config .label_definition_prefixes - .insert("asm".to_string(), "asm:".to_string()); + .push(("asm".to_string(), "asm:".to_string())); check_with_syntax_config( config, @@ -2195,11 +2195,11 @@ fn test_custom_label_prefix_custom_ref() { config.label_definition_commands.insert("asm".to_string()); config .label_definition_prefixes - .insert("asm".to_string(), "asm:".to_string()); + .push(("asm".to_string(), "asm:".to_string())); config.label_reference_commands.insert("asmref".to_string()); config .label_reference_prefixes - .insert("asmref".to_string(), "asm:".to_string()); + .push(("asmref".to_string(), "asm:".to_string())); check_with_syntax_config( config, diff --git a/crates/parser/src/config.rs b/crates/parser/src/config.rs index 9bded322..f433e822 100644 --- a/crates/parser/src/config.rs +++ b/crates/parser/src/config.rs @@ -1,4 +1,4 @@ -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; #[derive(Debug)] pub struct SyntaxConfig { @@ -8,9 +8,9 @@ pub struct SyntaxConfig { pub verbatim_environments: FxHashSet, pub citation_commands: FxHashSet, pub label_definition_commands: FxHashSet, - pub label_definition_prefixes: FxHashMap, + pub label_definition_prefixes: Vec<(String, String)>, pub label_reference_commands: FxHashSet, - pub label_reference_prefixes: FxHashMap, + pub label_reference_prefixes: Vec<(String, String)>, } impl Default for SyntaxConfig { diff --git a/crates/rename/src/label.rs b/crates/rename/src/label.rs index ca3fdac3..1d82f5e2 100644 --- a/crates/rename/src/label.rs +++ b/crates/rename/src/label.rs @@ -2,26 +2,27 @@ use base_db::{ semantics::{tex, Span}, util::queries::{self, Object}, }; -use rustc_hash::FxHashMap; use crate::{RenameBuilder, RenameInformation, RenameParams}; struct PrefixInformation<'a> { - def_prefixes: &'a FxHashMap, - ref_prefixes: &'a FxHashMap, + def_prefixes: &'a Vec<(String, String)>, + ref_prefixes: &'a Vec<(String, String)>, } fn label_has_prefix(pref_info: &PrefixInformation, label: &tex::Label) -> Option { match label.kind { - tex::LabelKind::Definition => pref_info - .def_prefixes - .get(&label.cmd.clone().unwrap_or(String::new())) - .cloned(), - _ => pref_info - .ref_prefixes - .get(&label.cmd.clone().unwrap_or(String::new())) - .cloned(), + tex::LabelKind::Definition => pref_info.def_prefixes.iter(), + _ => pref_info.ref_prefixes.iter(), } + .find_map(|(k, v)| { + if k == &label.cmd.clone().unwrap_or(String::new()) { + Some(v) + } else { + None + } + }) + .cloned() } fn find_prefix_in_any( diff --git a/crates/texlab/src/server/options.rs b/crates/texlab/src/server/options.rs index 3304c11f..1640c9cf 100644 --- a/crates/texlab/src/server/options.rs +++ b/crates/texlab/src/server/options.rs @@ -1,7 +1,5 @@ use serde::{Deserialize, Serialize}; -use rustc_hash::FxHashMap; - use regex::Regex; #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -128,9 +126,9 @@ pub struct ExperimentalOptions { pub verbatim_environments: Vec, pub citation_commands: Vec, pub label_definition_commands: Vec, - pub label_definition_prefixes: FxHashMap, + pub label_definition_prefixes: Vec<(String, String)>, pub label_reference_commands: Vec, - pub label_reference_prefixes: FxHashMap, + pub label_reference_prefixes: Vec<(String, String)>, } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]