on master help sign in
Add object.movement for tree-sitter navigation
ab2a0f3Gokul Soumya
book/src/guides/textobject.md+15
@@ -24,7 +24,22 @@ The following [captures][tree-sitter-captures] are recognized:
24
24
25[Example query files][textobject-examples] can be found in the helix GitHub repository.
25[Example query files][textobject-examples] can be found in the helix GitHub repository.
26
26
27+## Queries for Textobject Based Navigation
28+
29+[Tree-sitter based navigation][textobjects-nav] is done using captures in the
30+following order:
31+
32+- `object.movement`
33+- `object.around`
34+- `object.inside`
35+
36+For example if a `function.around` capture has been already defined for a language
37+in it's `textobjects.scm` file, function navigation should also work automatically.
38+`function.movement` should be defined only if the node captured by `function.around`
39+doesn't make sense in a navigation context.
40+
27[textobjects]: ../usage.md#textobjects
41[textobjects]: ../usage.md#textobjects
42+[textobjects-nav]: ../usage.md#tree-sitter-textobject-based-navigation
28[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
43[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
29[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
44[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
30[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
45[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
book/src/usage.md+1−1
@@ -75,7 +75,7 @@ document and a special tree-sitter query file to work properly. [Only
75some grammars][lang-support] currently have the query file implemented.
75some grammars][lang-support] currently have the query file implemented.
76Contributions are welcome!
76Contributions are welcome!
77
77
78−## Tree-sitter Based Navigation
78+## Tree-sitter Textobject Based Navigation
79
79
80Navigating between functions, classes, parameters, etc is made
80Navigating between functions, classes, parameters, etc is made
81possible by leveraging tree-sitter and textobjects queries. For
81possible by leveraging tree-sitter and textobjects queries. For
helix-core/src/movement.rs+7−3
@@ -321,10 +321,14 @@ pub fn goto_treesitter_object(
321 let get_range = move || -> Option<Range> {
321 let get_range = move || -> Option<Range> {
322 let byte_pos = slice.char_to_byte(range.cursor(slice));
322 let byte_pos = slice.char_to_byte(range.cursor(slice));
323
323
324− let capture_name = format!("{}.{}", object_name, TextObject::Around);
324+ let cap_name = |t: TextObject| format!("{}.{}", object_name, t);
325 let mut cursor = QueryCursor::new();
325 let mut cursor = QueryCursor::new();
326− let nodes = lang_config.textobject_query()?.capture_nodes(
326+ let nodes = lang_config.textobject_query()?.capture_nodes_any(
327− &capture_name,
327+ &[
328+ &cap_name(TextObject::Movement),
329+ &cap_name(TextObject::Around),
330+ &cap_name(TextObject::Inside),
331+ ],
328 slice_tree,
332 slice_tree,
329 slice,
333 slice,
330 &mut cursor,
334 &mut cursor,
helix-core/src/syntax.rs+15−1
@@ -188,7 +188,21 @@ impl TextObjectQuery {
188 slice: RopeSlice<'a>,
188 slice: RopeSlice<'a>,
189 cursor: &'a mut QueryCursor,
189 cursor: &'a mut QueryCursor,
190 ) -> Option<impl Iterator<Item = Node<'a>>> {
190 ) -> Option<impl Iterator<Item = Node<'a>>> {
191+ self.capture_nodes_any(&[capture_name], node, slice, cursor)
192+ }
193+
194+ /// Find the first capture that exists out of all given `capture_names`
195+ /// and return sub nodes that match this capture.
196+ pub fn capture_nodes_any<'a>(
197+ &'a self,
198+ capture_names: &[&str],
199+ node: Node<'a>,
200+ slice: RopeSlice<'a>,
201+ cursor: &'a mut QueryCursor,
202+ ) -> Option<impl Iterator<Item = Node<'a>>> {
203+ let capture_idx = capture_names
204+ .iter()
191− let capture_idx = self.query.capture_index_for_name(capture_name)?;
205+ .find_map(|cap| self.query.capture_index_for_name(cap))?;
192 let captures = cursor.captures(&self.query, node, RopeProvider(slice));
206 let captures = cursor.captures(&self.query, node, RopeProvider(slice));
193
207
194 captures
208 captures
helix-core/src/textobject.rs+5
@@ -53,6 +53,8 @@ fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction, lo
53pub enum TextObject {
53pub enum TextObject {
54 Around,
54 Around,
55 Inside,
55 Inside,
56+ /// Used for moving between objects.
57+ Movement,
56}
58}
57
59
58impl Display for TextObject {
60impl Display for TextObject {
@@ -60,6 +62,7 @@ impl Display for TextObject {
60 f.write_str(match self {
62 f.write_str(match self {
61 Self::Around => "around",
63 Self::Around => "around",
62 Self::Inside => "inside",
64 Self::Inside => "inside",
65+ Self::Movement => "movement",
63 })
66 })
64 }
67 }
65}
68}
@@ -104,6 +107,7 @@ pub fn textobject_word(
104 Range::new(word_start - whitespace_count_left, word_end)
107 Range::new(word_start - whitespace_count_left, word_end)
105 }
108 }
106 }
109 }
110+ TextObject::Movement => unreachable!(),
107 }
111 }
108}
112}
109
113
@@ -118,6 +122,7 @@ pub fn textobject_surround(
118 .map(|(anchor, head)| match textobject {
122 .map(|(anchor, head)| match textobject {
119 TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head),
123 TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head),
120 TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)),
124 TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)),
125+ TextObject::Movement => unreachable!(),
121 })
126 })
122 .unwrap_or(range)
127 .unwrap_or(range)
123}
128}