on master help sign in
match pairs which don't form a standalone TS node
37058e0Pascal Kuthe
helix-core/src/match_brackets.rs+99−13
@@ -1,8 +1,11 @@
1+use std::iter;
2+
1use tree_sitter::Node;
3use tree_sitter::Node;
2
4
3use crate::{Rope, Syntax};
5use crate::{Rope, Syntax};
4
6
5const MAX_PLAINTEXT_SCAN: usize = 10000;
7const MAX_PLAINTEXT_SCAN: usize = 10000;
8+const MATCH_LIMIT: usize = 16;
6
9
7// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
10// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
8const PAIRS: &[(char, char)] = &[
11const PAIRS: &[(char, char)] = &[
@@ -50,26 +53,55 @@ fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) ->
50 let tree = syntax.tree();
53 let tree = syntax.tree();
51 let pos = doc.char_to_byte(pos);
54 let pos = doc.char_to_byte(pos);
52
55
53− let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
56+ let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
54
57
55 loop {
58 loop {
59+ if node.is_named() {
56− let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
60+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
57− let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
61+ let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
58
62
59− if is_valid_pair(doc, start_char, end_char) {
63+ if is_valid_pair(doc, start_char, end_char) {
60− if end_byte == pos {
64+ if end_byte == pos {
61− return Some(start_char);
65+ return Some(start_char);
66+ }
67+ // We return the end char if the cursor is either on the start char
68+ // or at some arbitrary position between start and end char.
69+ return Some(end_char);
62 }
70 }
63− // We return the end char if the cursor is either on the start char
64− // or at some arbitrary position between start and end char.
65− return Some(end_char);
66 }
71 }
72+ // this node itselt wasn't a pair but maybe its siblings are
67
73
68− if traverse_parents {
74+ // check if we are *on* the pair (special cased so we don't look
69− node = node.parent()?;
75+ // at the current node twice and to jump to the start on that case)
70− } else {
76+ if let Some(open) = as_close_pair(doc, &node) {
77+ if let Some(pair_start) = find_pair_end(doc, node.prev_sibling(), open, Backward) {
71− return None;
78+ return Some(pair_start);
79+ }
72 }
80 }
81+
82+ if !traverse_parents {
83+ // check if we are *on* the opening pair (special cased here as
84+ // an opptimization since we only care about bracket on the cursor
85+ // here)
86+ if let Some(close) = as_open_pair(doc, &node) {
87+ if let Some(pair_end) = find_pair_end(doc, node.next_sibling(), close, Forward) {
88+ return Some(pair_end);
89+ }
90+ }
91+ if node.is_named() {
92+ return None;
93+ }
94+ }
95+
96+ for close in
97+ iter::successors(node.next_sibling(), |node| node.next_sibling()).take(MATCH_LIMIT)
98+ {
99+ let Some(open) = as_close_pair(doc, &close) else { continue; };
100+ if find_pair_end(doc, Some(node), open, Backward).is_some() {
101+ return doc.try_byte_to_char(close.start_byte()).ok();
102+ }
103+ }
104+ node = node.parent()?;
73 }
105 }
74}
106}
75
107
@@ -161,6 +193,60 @@ fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
161 Some((start_byte, end_byte))
193 Some((start_byte, end_byte))
162}
194}
163
195
196+/// Checks if `node` or its siblings (at most MATCH_LIMIT nodes) is the specified openiing char
197+///
198+/// # Returns
199+///
200+/// The position of the found node or `None` otherwise
201+fn find_open_pair(doc: &Rope, node: Option<Node>, open: char) -> Option<usize> {
202+ iter::successors(node, |node| node.prev_sibling())
203+ .take(MATCH_LIMIT)
204+ .find_map(|node| {
205+ let (pos, c) = as_char(doc, &node)?;
206+ (c == open).then_some(pos)
207+ })
208+}
209+
210+/// Tests if this node is a pair close char and returns the expected open char
211+fn as_close_pair(doc: &Rope, node: &Node) -> Option<char> {
212+ let close = as_char(doc, node)?.1;
213+ PAIRS
214+ .iter()
215+ .find_map(|&(open, close_)| (close_ == close).then_some(open))
216+}
217+
218+/// Checks if `node` or its siblings (at most MATCH_LIMIT nodes) is the specified closing char
219+///
220+/// # Returns
221+///
222+/// The position of the found node or `None` otherwise
223+fn find_close_pair(doc: &Rope, node: Option<Node>, close: char) -> Option<usize> {
224+ iter::successors(node, |node| node.next_sibling())
225+ .take(MATCH_LIMIT)
226+ .find_map(|node| {
227+ let (pos, c) = as_char(doc, &node)?;
228+ (end_char == c).then_some(pos)
229+ })
230+}
231+
232+/// Tests if this node is a pair close char and returns the expected open char
233+fn as_open_pair(doc: &Rope, node: &Node) -> Option<char> {
234+ let close = as_char(doc, node)?.1;
235+ PAIRS
236+ .iter()
237+ .find_map(|&(open_, close)| (open_ == open).then_some(close))
238+}
239+
240+/// Tests if this node is a pair opening and returns the expected close char
241+fn as_char(doc: &Rope, node: &Node) -> Option<(usize, char)> {
242+ // TODO: multi char/non ASCII pairs
243+ if node.byte_range().len() != 1 {
244+ return None;
245+ }
246+ let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
247+ Some((pos, doc.char(pos)))
248+}
249+
164#[cfg(test)]
250#[cfg(test)]
165mod tests {
251mod tests {
166 use super::*;
252 use super::*;