feat: highlight rust string interpolation macros that use `format_args!` (#13533)

Co-authored-by: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com>
pull/11441/head
Nik Revenco 2025-05-24 16:02:32 +01:00 committed by GitHub
parent 223ceec10a
commit 1023e8f964
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 171 additions and 6 deletions

View File

@ -199,6 +199,7 @@
| rst | ✓ | | | |
| ruby | ✓ | ✓ | ✓ | `ruby-lsp`, `solargraph` |
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
| rust-format-args | ✓ | | | |
| sage | ✓ | ✓ | | |
| scala | ✓ | ✓ | ✓ | `metals` |
| scheme | ✓ | | ✓ | |

View File

@ -734,7 +734,7 @@ async fn surround_replace_ts() -> anyhow::Result<()> {
const INPUT: &str = r#"\
fn foo() {
if let Some(_) = None {
todo!("f#[|o]#o)");
testing!("f#[|o]#o)");
}
}
"#;
@ -744,7 +744,7 @@ fn foo() {
r#"\
fn foo() {
if let Some(_) = None {
todo!('f#[|o]#o)');
testing!('f#[|o]#o)');
}
}
"#,
@ -757,7 +757,7 @@ fn foo() {
r#"\
fn foo() {
if let Some(_) = None [
todo!("f#[|o]#o)");
testing!("f#[|o]#o)");
]
}
"#,
@ -770,7 +770,7 @@ fn foo() {
r#"\
fn foo() {
if let Some(_) = None {
todo!{"f#[|o]#o)"};
testing!{"f#[|o]#o)"};
}
}
"#,

View File

@ -379,9 +379,9 @@ async fn match_around_closest_ts() -> anyhow::Result<()> {
test_with_config(
AppBuilder::new().with_file("foo.rs", None),
(
r#"fn main() {todo!{"f#[|oo]#)"};}"#,
r#"fn main() {testing!{"f#[|oo]#)"};}"#,
"mam",
r#"fn main() {todo!{#[|"foo)"]#};}"#,
r#"fn main() {testing!{#[|"foo)"]#};}"#,
),
)
.await?;

View File

@ -4360,3 +4360,13 @@ file-types = [ { glob = "dunst/dunstrc" } ]
[[grammar]]
name = "dunstrc"
source = { git = "https://github.com/rotmh/tree-sitter-dunstrc", rev = "9cb9d5cc51cf5e2a47bb2a0e2f2e519ff11c1431" }
[[language]]
name = "rust-format-args"
scope = "source.rust-format-args"
file-types = []
injection-regex = "rust-format-args"
[[grammar]]
name = "rust-format-args"
source = { git = "https://github.com/nik-rev/tree-sitter-rustfmt", rev = "2ca0bdd763d0c9dbb1d0bd14aea7544cbe81309c" }

View File

@ -0,0 +1,30 @@
; regular escapes like `\n` are detected using another grammar
; Here, we only detect `{{` and `}}` as escapes for `{` and `}`
(escaped) @constant.character.escape
[
"#"
(type)
] @special
[
(sign)
(fill)
(align)
(width)
] @operator
(number) @constant.numeric
(colon) @punctuation
(identifier) @variable
; SCREAMING_CASE is assumed to be constant
((identifier) @constant
(#match? @constant "^[A-Z_]+$"))
[
"{"
"}"
] @punctuation.special

View File

@ -97,3 +97,127 @@
]
)
(#set! injection.language "sql"))
; Special language `tree-sitter-rust-format-args` for Rust macros,
; which use `format_args!` under the hood and therefore have
; the `format_args!` syntax.
;
; This language is injected into a hard-coded set of macros.
; 1st argument is `format_args!`
(
(macro_invocation
macro:
[
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
(token_tree
. (string_literal
(string_content) @injection.content
)
)
)
(#any-of? @_macro_name
; std
"print" "println" "eprint" "eprintln"
"format" "format_args" "todo" "panic"
"unreachable" "unimplemented" "compile_error"
; log
"crit" "trace" "debug" "info" "warn" "error"
; anyhow
"anyhow" "bail"
; syn
"format_ident"
; indoc
"formatdoc" "printdoc" "eprintdoc" "writedoc"
; iced
"text"
; ratatui
"span"
; eyre
"eyre"
; miette
"miette"
)
(#set! injection.language "rust-format-args")
(#set! injection.include-children)
)
; 2nd argument is `format_args!`
(
(macro_invocation
macro:
[
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
(token_tree
. (_)
. (string_literal
(string_content) @injection.content
)
)
)
(#any-of? @_macro_name
; std
"write" "writeln" "assert" "debug_assert"
; defmt
"expect" "unwrap"
; ratatui
"span"
)
(#set! injection.language "rust-format-args")
(#set! injection.include-children)
)
; 3rd argument is `format_args!`
(
(macro_invocation
macro:
[
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
(token_tree
. (_)
. (_)
. (string_literal
(string_content) @injection.content
)
)
)
(#any-of? @_macro_name
; std
"assert_eq" "debug_assert_eq" "assert_ne" "debug_assert_ne"
)
(#set! injection.language "rust-format-args")
(#set! injection.include-children)
)
; Dioxus' "rsx!" macro relies heavily on string interpolation as well. The strings can be nested very deeply
(
(macro_invocation
macro: [
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
; TODO: This only captures 1 level of string literals. But in dioxus you can have
; nested string literals. For instance:
;
; rsx! { "{hello} world" }:
; -> (token_tree (string_literal))
; rsx! { div { "{hello} world" } }
; -> (token_tree (token_tree (string_literal)))
; rsx! { div { div { "{hello} world" } } }
; -> (token_tree (token_tree (token_tree (string_literal))))
(token_tree (string_literal) @injection.content)
)
(#eq? @_macro_name "rsx")
(#set! injection.language "rust-format-args")
(#set! injection.include-children)
)