diff options
author | Norbert Preining <norbert@preining.info> | 2024-06-24 03:01:38 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2024-06-24 03:01:38 +0000 |
commit | 81c3fb3cf620cd2f980ef58b160563c5a809674a (patch) | |
tree | 378663671bcffe8d2b9e16aba4428e591a25a27e /support/texlab | |
parent | 6abdc79a45a3d88a2e0a33a28a364efdb1f597c5 (diff) |
CTAN sync 202406240301
Diffstat (limited to 'support/texlab')
23 files changed, 558 insertions, 80 deletions
diff --git a/support/texlab/CHANGELOG.md b/support/texlab/CHANGELOG.md index 8008720d9b..007f344876 100644 --- a/support/texlab/CHANGELOG.md +++ b/support/texlab/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.17.0] - 2024-06-23 + +### Added + +- Add label commands from `zref` and `zref-clever` to the list of default label commands + ([#1140](https://github.com/latex-lsp/texlab/pull/1140)) +- Add `texlab.experimental.labelDefinitionPrefixes` and + `texlab.experimental.labelReferencePrefixes` options ([#1139](https://github.com/latex-lsp/texlab/pull/1139)) + ## [5.16.1] - 2024-05-25 ### Fixed diff --git a/support/texlab/Cargo.lock b/support/texlab/Cargo.lock index 9d651234e1..09847a2a79 100644 --- a/support/texlab/Cargo.lock +++ b/support/texlab/Cargo.lock @@ -334,6 +334,7 @@ dependencies = [ "expect-test", "fuzzy-matcher", "line-index", + "parser", "rayon", "rowan", "rustc-hash", @@ -860,9 +861,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "line-index" @@ -1094,9 +1095,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1337,6 +1338,7 @@ name = "rename" version = "0.0.0" dependencies = [ "base-db", + "parser", "rowan", "rustc-hash", "syntax", @@ -1548,7 +1550,7 @@ dependencies = [ [[package]] name = "texlab" -version = "5.16.1" +version = "5.17.0" dependencies = [ "anyhow", "base-db", @@ -1601,18 +1603,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/support/texlab/crates/base-db/src/document.rs b/support/texlab/crates/base-db/src/document.rs index 7a388129fd..afb5869d69 100644 --- a/support/texlab/crates/base-db/src/document.rs +++ b/support/texlab/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/support/texlab/crates/base-db/src/semantics/tex.rs b/support/texlab/crates/base-db/src/semantics/tex.rs index 7e4c926f5d..a1301091d4 100644 --- a/support/texlab/crates/base-db/src/semantics/tex.rs +++ b/support/texlab/crates/base-db/src/semantics/tex.rs @@ -3,7 +3,28 @@ use rustc_hash::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: &Vec<(String, String)>, + command: &Option<SyntaxToken>, + name: &Span, +) -> Span { + match command { + Some(x) => Span::new( + 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(), + } +} #[derive(Debug, Clone, Default)] pub struct Semantics { @@ -19,11 +40,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 +61,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 +132,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 +211,14 @@ impl Semantics { self.labels.push(Label { kind: LabelKind::Definition, - name, + 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, }); } - 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 +229,12 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::Reference, - name, + cmd: label.command().map(|x| x.text()[1..].to_string()), + name: maybe_prepend_prefix( + &conf.label_reference_prefixes, + &label.command(), + &name, + ), targets: Vec::new(), full_range, }); @@ -215,14 +242,23 @@ 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, + cmd: label.command().map(|x| x.text()[1..].to_string()), + name: maybe_prepend_prefix( + &conf.label_reference_prefixes, + &label.command(), + &name, + ), targets: Vec::new(), full_range, }); @@ -234,7 +270,12 @@ impl Semantics { if !name.text.contains('#') { self.labels.push(Label { kind: LabelKind::ReferenceRange, - name, + cmd: label.command().map(|x| x.text()[1..].to_string()), + name: maybe_prepend_prefix( + &conf.label_reference_prefixes, + &label.command(), + &name, + ), targets: Vec::new(), full_range, }); @@ -336,6 +377,7 @@ pub enum LabelKind { #[derive(Debug, Clone)] pub struct Label { pub kind: LabelKind, + pub cmd: Option<String>, pub name: Span, pub targets: Vec<LabelTarget>, pub full_range: TextRange, diff --git a/support/texlab/crates/commands/Cargo.toml b/support/texlab/crates/commands/Cargo.toml index 095828fbc5..6e415f20d6 100644 --- a/support/texlab/crates/commands/Cargo.toml +++ b/support/texlab/crates/commands/Cargo.toml @@ -12,12 +12,12 @@ base-db = { path = "../base-db" } bstr = "1.9.1" crossbeam-channel = "0.5.12" itertools = "0.12.1" -libc = "0.2.154" +libc = "0.2.155" log = "0.4.21" rowan = "0.15.15" rustc-hash = "1.1.0" syntax = { path = "../syntax" } -thiserror = "1.0.59" +thiserror = "1.0.61" url = "2.5.0" [dev-dependencies] diff --git a/support/texlab/crates/completion/Cargo.toml b/support/texlab/crates/completion/Cargo.toml index e28e04dfc7..6469b5f004 100644 --- a/support/texlab/crates/completion/Cargo.toml +++ b/support/texlab/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/support/texlab/crates/completion/src/lib.rs b/support/texlab/crates/completion/src/lib.rs index b1050052f0..5bfd94d944 100644 --- a/support/texlab/crates/completion/src/lib.rs +++ b/support/texlab/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, diff --git a/support/texlab/crates/completion/src/providers/label_ref.rs b/support/texlab/crates/completion/src/providers/label_ref.rs index 6f71ae3241..8b262f13ca 100644 --- a/support/texlab/crates/completion/src/providers/label_ref.rs +++ b/support/texlab/crates/completion/src/providers/label_ref.rs @@ -11,12 +11,30 @@ use crate::{ CompletionItem, CompletionItemData, CompletionParams, }; +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>( 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 + .iter() + .find_map(|(k, v)| if *k == command { Some(v) } else { None }) + .map(|x| x.as_str()); for document in ¶ms.feature.project.documents { let DocumentData::Tex(data) = &document.data else { @@ -29,6 +47,10 @@ 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; + } + 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 +63,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 +82,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.to_string(), }); builder @@ -82,20 +106,26 @@ pub fn complete_label_references<'a>( struct FindResult { cursor: Span, is_math: bool, + command: String, } fn find_reference(params: &CompletionParams) -> Option<FindResult> { 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<FindResult> { 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/support/texlab/crates/completion/src/tests.rs b/support/texlab/crates/completion/src/tests.rs index c6bed6e08e..8c68b71dd9 100644 --- a/support/texlab/crates/completion/src/tests.rs +++ b/support/texlab/crates/completion/src/tests.rs @@ -1,11 +1,17 @@ -use base_db::FeatureParams; +use base_db::{Config, FeatureParams}; use expect_test::{expect, Expect}; +use parser::SyntaxConfig; use rowan::TextRange; 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 +43,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 +2144,200 @@ 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_definition_prefixes + .push(("asm".to_string(), "asm:".to_string())); + + check_with_syntax_config( + config, + r#" +%! main.tex + \begin{enumerate}\label{baz} + \asm{foo}{what} + \end{enumerate} + + \ref{} + | +% 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", + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_custom_label_prefix_custom_ref() { + let mut config = SyntaxConfig::default(); + config.label_definition_commands.insert("asm".to_string()); + config + .label_definition_prefixes + .push(("asm".to_string(), "asm:".to_string())); + config.label_reference_commands.insert("asmref".to_string()); + config + .label_reference_prefixes + .push(("asmref".to_string(), "asm:".to_string())); + + check_with_syntax_config( + config, + r#" +%! main.tex + \begin{enumerate}\label{baz} + \asm{foo}{what} + \end{enumerate} + + \asmref{} + | +% Comment"#, + expect![[r#" + [ + Label( + LabelData { + name: "foo", + header: None, + footer: None, + object: None, + 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_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#" +%! main.tex + \begin{enumerate}\label{baz} + \asm{foo}{what} + \goal{foo}{what} + \end{enumerate} + + \goalref{} + | +% Comment"#, + expect![[r#" + [ + Label( + LabelData { + name: "foo", + header: None, + footer: None, + object: None, + keywords: "foo", + }, + ), + ] + "#]], + ); +} + +#[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_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#" +%! main.tex + \begin{enumerate}\label{baz} + \asm{foo}{what} + \goal{foo}{what} + \end{enumerate} + + \ref{} + | +% 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", + }, + ), + ] + "#]], + ); +} diff --git a/support/texlab/crates/parser/src/config.rs b/support/texlab/crates/parser/src/config.rs index f81083f132..95b3e4eb56 100644 --- a/support/texlab/crates/parser/src/config.rs +++ b/support/texlab/crates/parser/src/config.rs @@ -8,7 +8,9 @@ pub struct SyntaxConfig { pub verbatim_environments: FxHashSet<String>, pub citation_commands: FxHashSet<String>, pub label_definition_commands: FxHashSet<String>, + pub label_definition_prefixes: Vec<(String, String)>, pub label_reference_commands: FxHashSet<String>, + pub label_reference_prefixes: Vec<(String, String)>, } 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, } } } @@ -170,7 +184,9 @@ static DEFAULT_CITATION_COMMANDS: &[&str] = &[ "citeA*", ]; -static DEFAULT_LABEL_DEFINITION_COMMANDS: &[&str] = &["label"]; +static DEFAULT_LABEL_DEFINITION_COMMANDS: &[&str] = &["label", "zlabel"]; + +static DEFAULT_LABEL_DEFINITION_PREFIXES: &[(&str, &str)] = &[]; static DEFAULT_LABEL_REFERENCE_COMMANDS: &[&str] = &[ "ref", @@ -182,6 +198,10 @@ static DEFAULT_LABEL_REFERENCE_COMMANDS: &[&str] = &[ "cref*", "Cref", "Cref*", + "zcref", + "zcref*", + "zcpageref", + "zcpageref*", "namecref", "nameCref", "lcnamecref", @@ -192,3 +212,5 @@ static DEFAULT_LABEL_REFERENCE_COMMANDS: &[&str] = &[ "labelcpageref", "eqref", ]; + +static DEFAULT_LABEL_REFERENCE_PREFIXES: &[(&str, &str)] = &[]; diff --git a/support/texlab/crates/rename/Cargo.toml b/support/texlab/crates/rename/Cargo.toml index 1b2b91ee2a..dc7558515a 100644 --- a/support/texlab/crates/rename/Cargo.toml +++ b/support/texlab/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/support/texlab/crates/rename/src/command.rs b/support/texlab/crates/rename/src/command.rs index 7b17d4271c..037e09e4ca 100644 --- a/support/texlab/crates/rename/src/command.rs +++ b/support/texlab/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/support/texlab/crates/rename/src/entry.rs b/support/texlab/crates/rename/src/entry.rs index 5bed4ae613..d35d9e9e84 100644 --- a/support/texlab/crates/rename/src/entry.rs +++ b/support/texlab/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/support/texlab/crates/rename/src/label.rs b/support/texlab/crates/rename/src/label.rs index b21e1b795e..1d82f5e2b4 100644 --- a/support/texlab/crates/rename/src/label.rs +++ b/support/texlab/crates/rename/src/label.rs @@ -3,22 +3,65 @@ use base_db::{ util::queries::{self, Object}, }; -use crate::{RenameBuilder, RenameParams}; +use crate::{RenameBuilder, RenameInformation, RenameParams}; + +struct PrefixInformation<'a> { + def_prefixes: &'a Vec<(String, String)>, + ref_prefixes: &'a Vec<(String, String)>, +} + +fn label_has_prefix(pref_info: &PrefixInformation, label: &tex::Label) -> Option<String> { + match label.kind { + 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( + builder: &mut RenameBuilder, + pref_info: &PrefixInformation, + name: &str, +) -> Option<String> { + let project = &builder.params.feature.project; + queries::objects_with_name::<tex::Label>(project, name) + .find_map(|(_, label)| label_has_prefix(&pref_info, label)) +} pub(super) fn prepare_rename(params: &RenameParams) -> Option<Span> { 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)?; + 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::<tex::Label>(project, &name.text) { + 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/support/texlab/crates/rename/src/lib.rs b/support/texlab/crates/rename/src/lib.rs index bf9023ddef..e6c05ad268 100644 --- a/support/texlab/crates/rename/src/lib.rs +++ b/support/texlab/crates/rename/src/lib.rs @@ -13,8 +13,14 @@ pub struct RenameParams<'a> { } #[derive(Debug, Default)] +pub struct RenameInformation { + pub range: TextRange, + pub prefix: Option<String>, +} + +#[derive(Debug, Default)] pub struct RenameResult<'a> { - pub changes: FxHashMap<&'a Document, Vec<TextRange>>, + pub changes: FxHashMap<&'a Document, Vec<RenameInformation>>, } struct RenameBuilder<'a> { @@ -22,6 +28,20 @@ struct RenameBuilder<'a> { result: RenameResult<'a>, } +impl From<TextRange> for RenameInformation { + fn from(range: TextRange) -> Self { + RenameInformation { + range, + prefix: None, + } + } +} +impl PartialEq for RenameInformation { + fn eq(&self, other: &Self) -> bool { + self.range == other.range + } +} + pub fn prepare_rename(params: &RenameParams) -> Option<TextRange> { command::prepare_rename(params) .or_else(|| entry::prepare_rename(params)) diff --git a/support/texlab/crates/rename/src/tests.rs b/support/texlab/crates/rename/src/tests.rs index 0f4263033e..4da866fb08 100644 --- a/support/texlab/crates/rename/src/tests.rs +++ b/support/texlab/crates/rename/src/tests.rs @@ -1,23 +1,37 @@ use rustc_hash::FxHashMap; -use crate::RenameParams; +use base_db::Config; +use parser::SyntaxConfig; -fn check(input: &str) { - let fixture = test_utils::fixture::Fixture::parse(input); +use crate::{RenameInformation, RenameParams}; + +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(); + let mut expected: FxHashMap<_, Vec<RenameInformation>> = 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(); 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( @@ -78,12 +92,97 @@ fn test_label() { | ^^^ +%! baz.tex +\ref{foo} + %! bar.tex \ref{foo} ^^^ +"#, + ) +} + +#[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} + ^^^^^^^ "#, ) } diff --git a/support/texlab/crates/texlab/Cargo.toml b/support/texlab/crates/texlab/Cargo.toml index a2fb78e11f..fa419413f5 100644 --- a/support/texlab/crates/texlab/Cargo.toml +++ b/support/texlab/crates/texlab/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "texlab" description = "LaTeX Language Server" -version = "5.16.1" +version = "5.17.0" license.workspace = true readme = "README.md" authors.workspace = true @@ -47,7 +47,7 @@ lsp-server = "0.7.6" lsp-types = "0.95.1" notify = "6.1.1" notify-debouncer-full = "0.3.1" -parking_lot = "0.12.2" +parking_lot = "0.12.3" parser = { path = "../parser" } references = { path = "../references" } regex = "1.10.4" diff --git a/support/texlab/crates/texlab/src/server/options.rs b/support/texlab/crates/texlab/src/server/options.rs index 3f50faefd7..1640c9cf7a 100644 --- a/support/texlab/crates/texlab/src/server/options.rs +++ b/support/texlab/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<String>, pub citation_commands: Vec<String>, pub label_definition_commands: Vec<String>, + pub label_definition_prefixes: Vec<(String, String)>, pub label_reference_commands: Vec<String>, + pub label_reference_prefixes: Vec<(String, String)>, } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] diff --git a/support/texlab/crates/texlab/src/util/client_flags.rs b/support/texlab/crates/texlab/src/util/client_flags.rs index 842dbb1eae..2829041832 100644 --- a/support/texlab/crates/texlab/src/util/client_flags.rs +++ b/support/texlab/crates/texlab/src/util/client_flags.rs @@ -20,18 +20,12 @@ pub struct ClientFlags { /// If not set, then fuzzy citation completion will not work. pub completion_always_incomplete: bool, - /// If `true`, the server can include markdown in hover responses. - pub hover_markdown: bool, - /// If `true`, the server can pull the configuration from the client. pub configuration_pull: bool, /// If `true`, the client notifies the server when the configuration changes. pub configuration_push: bool, - /// If `true`, the client can return `LocationLink` instead of `Location`. - pub definition_link: bool, - /// If `true`, the server can return custom kinds like `section`. pub folding_custom_kinds: bool, diff --git a/support/texlab/crates/texlab/src/util/from_proto.rs b/support/texlab/crates/texlab/src/util/from_proto.rs index 789edbf9c3..b55e3cb0e3 100644 --- a/support/texlab/crates/texlab/src/util/from_proto.rs +++ b/support/texlab/crates/texlab/src/util/from_proto.rs @@ -56,15 +56,6 @@ pub fn client_flags( let completion_always_incomplete = info.map_or(false, |info| info.name == "Visual Studio Code"); - let hover_markdown = capabilities - .text_document - .as_ref() - .and_then(|cap| cap.hover.as_ref()) - .and_then(|cap| cap.content_format.as_ref()) - .map_or(false, |formats| { - formats.contains(&lsp_types::MarkupKind::Markdown) - }); - let configuration_pull = capabilities .workspace .as_ref() @@ -78,13 +69,6 @@ pub fn client_flags( .and_then(|cap| cap.dynamic_registration) .unwrap_or(false); - let definition_link = capabilities - .text_document - .as_ref() - .and_then(|cap| cap.definition) - .and_then(|cap| cap.link_support) - .unwrap_or(false); - let folding_custom_kinds = capabilities .text_document .as_ref() @@ -111,10 +95,8 @@ pub fn client_flags( completion_snippets, completion_kinds, completion_always_incomplete, - hover_markdown, configuration_pull, configuration_push, - definition_link, folding_custom_kinds, progress, show_document, @@ -358,8 +340,18 @@ pub fn config(value: Options) -> Config { 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 } diff --git a/support/texlab/crates/texlab/src/util/to_proto.rs b/support/texlab/crates/texlab/src/util/to_proto.rs index f5cf2edf8b..d2c261c8fb 100644 --- a/support/texlab/crates/texlab/src/util/to_proto.rs +++ b/support/texlab/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); } diff --git a/support/texlab/texlab.1 b/support/texlab/texlab.1 index 69f16a0bf9..d829a081f9 100644 --- a/support/texlab/texlab.1 +++ b/support/texlab/texlab.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1. -.TH TEXLAB "1" "May 2024" "texlab 5.16.1" "User Commands" +.TH TEXLAB "1" "June 2024" "texlab 5.17.0" "User Commands" .SH NAME -texlab \- manual page for texlab 5.16.1 +texlab \- manual page for texlab 5.17.0 .SH SYNOPSIS .B texlab [\fI\,OPTIONS\/\fR] [\fI\,COMMAND\/\fR] diff --git a/support/texlab/texlab.pdf b/support/texlab/texlab.pdf Binary files differindex 8731c5ef78..828032ce0c 100644 --- a/support/texlab/texlab.pdf +++ b/support/texlab/texlab.pdf |