diff --git a/CHANGELOG.md b/CHANGELOG.md index 27687fee..41840012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replace tilde (`~`), environment variables, `${userHome}`, `${workspaceFolder}` in options - Replace tidle (`~`) with home directory in `\include`-like commands - Add "Go To Definition" support for user-defined commands with `\def` and `\let` ([#1081](https://github.com/latex-lsp/texlab/issues/1081)) +- Add "Find all References" for commands ([#1082](https://github.com/latex-lsp/texlab/issues/1082)) ## [5.14.1] - 2024-03-27 diff --git a/Cargo.lock b/Cargo.lock index 941b7060..f0d0061f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1236,6 +1236,7 @@ version = "0.0.0" dependencies = [ "base-db", "rowan", + "rustc-hash", "syntax", "test-utils", ] diff --git a/crates/base-db/src/semantics.rs b/crates/base-db/src/semantics.rs index f83d758c..a2f0de29 100644 --- a/crates/base-db/src/semantics.rs +++ b/crates/base-db/src/semantics.rs @@ -1,3 +1,5 @@ +use rowan::{TextLen, TextRange}; + pub mod auxiliary; pub mod bib; pub mod tex; @@ -19,6 +21,13 @@ impl Span { range: rowan::TextRange::empty(offset), } } + + pub fn command(token: &rowan::SyntaxToken) -> Self { + let range = token.text_range(); + let range = TextRange::new(range.start() + "\\".text_len(), range.end()); + let text = String::from(&token.text()[1..]); + Self::new(text, range) + } } impl std::fmt::Debug for Span { diff --git a/crates/base-db/src/semantics/tex.rs b/crates/base-db/src/semantics/tex.rs index 659ec8c1..9d88489f 100644 --- a/crates/base-db/src/semantics/tex.rs +++ b/crates/base-db/src/semantics/tex.rs @@ -1,4 +1,4 @@ -use rowan::{ast::AstNode, TextLen, TextRange}; +use rowan::{ast::AstNode, TextRange}; use rustc_hash::FxHashSet; use syntax::latex::{self, HasBrack, HasCurly}; @@ -26,10 +26,7 @@ impl Semantics { } latex::SyntaxElement::Token(token) => { if token.kind() == latex::COMMAND_NAME { - let range = token.text_range(); - let range = TextRange::new(range.start() + "\\".text_len(), range.end()); - let text = String::from(&token.text()[1..]); - self.commands.push(Span { range, text }); + self.commands.push(Span::command(&token)); } } }; diff --git a/crates/references/Cargo.toml b/crates/references/Cargo.toml index b2d80d49..e78e84ea 100644 --- a/crates/references/Cargo.toml +++ b/crates/references/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] base-db = { path = "../base-db" } rowan = "0.15.15" +rustc-hash = "1.1.0" syntax = { path = "../syntax" } [dev-dependencies] diff --git a/crates/references/src/command.rs b/crates/references/src/command.rs new file mode 100644 index 00000000..712da20b --- /dev/null +++ b/crates/references/src/command.rs @@ -0,0 +1,52 @@ +use base_db::{semantics::Span, DocumentLocation}; +use rowan::ast::AstNode; +use rustc_hash::FxHashSet; +use syntax::latex; + +use crate::{Reference, ReferenceContext, ReferenceKind}; + +pub(super) fn find_all(context: &mut ReferenceContext) -> Option<()> { + let data = context.params.feature.document.data.as_tex()?; + let token = data + .root_node() + .token_at_offset(context.params.offset) + .find(|token| token.kind() == latex::COMMAND_NAME)?; + + let project = &context.params.feature.project; + + for document in &project.documents { + if let Some(data) = document.data.as_tex() { + let defs: FxHashSet = data + .root_node() + .descendants() + .filter_map(|node| { + latex::OldCommandDefinition::cast(node.clone()) + .and_then(|node| node.name()) + .or_else(|| { + latex::NewCommandDefinition::cast(node) + .and_then(|node| node.name()) + .and_then(|group| group.command()) + }) + .map(|name| Span::command(&name)) + }) + .collect(); + + for command in &data.semantics.commands { + if command.text == &token.text()[1..] { + let kind = if defs.contains(command) { + ReferenceKind::Definition + } else { + ReferenceKind::Reference + }; + + context.results.push(Reference { + location: DocumentLocation::new(document, command.range), + kind, + }); + } + } + } + } + + Some(()) +} diff --git a/crates/references/src/lib.rs b/crates/references/src/lib.rs index 23dbd7e0..7d14a20d 100644 --- a/crates/references/src/lib.rs +++ b/crates/references/src/lib.rs @@ -1,3 +1,4 @@ +mod command; mod entry; mod label; mod string_def; @@ -39,6 +40,7 @@ pub fn find_all<'a>(params: &ReferenceParams<'a>) -> Vec> { entry::find_all(&mut context); label::find_all(&mut context); string_def::find_all(&mut context); + command::find_all(&mut context); context .results diff --git a/crates/references/src/tests.rs b/crates/references/src/tests.rs index a21a3ce5..845282af 100644 --- a/crates/references/src/tests.rs +++ b/crates/references/src/tests.rs @@ -229,3 +229,34 @@ fn test_string_definition_include_decl() { true, ); } + +#[test] +fn test_new_command_definition() { + check( + r#" +%! main.tex +\foo + | + ^^^ + +\newcommand{\foo}{foo} +"#, + false, + ); +} + +#[test] +fn test_new_command_definition_include_decl() { + check( + r#" +%! main.tex +\foo + | + ^^^ + +\newcommand{\foo}{foo} + ^^^ +"#, + true, + ); +}