Treesitter query to match adjacent Rust nodes?
Treesitter query to match adjacent Rust nodes?
I'd like a treesitter query that matches a Rust struct together with all of its attributes. For example,
#[derive(Debug)]
#[serde(rename_all = "camel_case")]
pub struct MyType {
pub foo: i32,
}
The lines beginning with #
are attributes that are logically connected to the struct declaration. But the treesitter grammar for Rust parses attributes as adjacent nodes, not as children of the struct declaration:
(attribute_item ; [27, 0] - [27, 16]
(attribute ; [27, 2] - [27, 15]
(identifier) ; [27, 2] - [27, 8]
arguments: (token_tree ; [27, 8] - [27, 15]
(identifier)))) ; [27, 9] - [27, 14]
(attribute_item ; [28, 0] - [28, 35]
(attribute ; [28, 2] - [28, 34]
(identifier) ; [28, 2] - [28, 7]
arguments: (token_tree ; [28, 7] - [28, 34]
(identifier) ; [28, 8] - [28, 18]
(string_literal)))) ; [28, 21] - [28, 33]
(struct_item ; [29, 0] - [31, 1]
(visibility_modifier) ; [29, 0] - [29, 3]
name: (type_identifier) ; [29, 11] - [29, 17]
body: (field_declaration_list ; [29, 18] - [31, 1]
(field_declaration ; [30, 4] - [30, 16]
(visibility_modifier) ; [30, 4] - [30, 7]
name: (field_identifier) ; [30, 8] - [30, 11]
type: (primitive_type)))) ; [30, 13] - [30, 16]
How can I get produce a query that I can use in mini.ai that matches the struct, and all attributes?
I've tried this query using Neovim's new built-in :EditQuery
command:
((attribute_item)* . (struct_item)) @custom_capture.outer
It looks like it does what I want. But when I try using @custom_capture.outer
in mini.ai it matches the struct declaration, but not the attributes.
I tried using #make-range!
like this,
((attribute_item)* @_start . (struct_item) @_end
(#make-range! "custom_capture.outer" @_start @_end))
That matches the struct and the second attribute, but does not get the first attribute. I'm guessing that's because the .
specifies that nodes must be adjacent, and the second attribute is the only one that is adjacent to a struct_item. Following that thinking I tried this,
((attribute_item)? @_start . (attribute_item)* . (struct_item) @_end
(#make-range! "custom_capture.outer" @_start @_end))
That gets the struct and all the attributes, but only if my cursor is on the first attribute line when I use the textobject. If my cursor is on any subsequent line then I get the second attribute and the struct, but the first attribute is missed.
One problem is I'm not clear whether ((attribute_item) . (struct_item))
matches an attribute_item and a struct_item that are adjacent, or matches an attribute_item that precedes a struct_item, but does not also match the struct_item. I tried experimenting with the second interpretation and used this query,
(((attribute_item)
. [(attribute_item) (struct_item)])* @_start
(struct_item) @_end
(#make-range! "custom_capture.outer" @_start @_end))
That captures what I want, but in some cases if I have two struct declarations and I try to match only the second one the query selects both structs instead.
Is that the way to do a lookahead? Or is there another way?
I've kinda hit a wall looking at documentation, other examples, and running my own experiments. Does anyone have any pointers to help understand these queries on a deeper level?
Edit: It looks like this stuff is in flux, so I should mention that I'm using the latest nightly as of March 2 2024, and I made sure that all of my plugins are up-to-date.