@@ -1,8 +1,11 @@
1+use std::iter;
2+
13use tree_sitter::Node;
24
35use crate::{Rope, Syntax};
46
57const MAX_PLAINTEXT_SCAN: usize = 10000;
8+const MATCH_LIMIT: usize = 16;
69
710// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
811const PAIRS: &[(char, char)] = &[
@@ -50,26 +53,55 @@ fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) ->
5053 let tree = syntax.tree();
5154 let pos = doc.char_to_byte(pos);
5255
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)?;
5457
5558 loop {
56− 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));
59+ if node.is_named() {
60+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
61+ let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
5862
59− if is_valid_pair(doc, start_char, end_char) {
60− if end_byte == pos {
61− return Some(start_char);
63+ if is_valid_pair(doc, start_char, end_char) {
64+ if end_byte == pos {
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);
6270 }
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);
6671 }
72+ // this node itselt wasn't a pair but maybe its siblings are
6773
68− if traverse_parents {
69− node = node.parent()?;
70− } else {
71− return None;
74+ // check if we are *on* the pair (special cased so we don't look
75+ // at the current node twice and to jump to the start on that case)
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) {
78+ return Some(pair_start);
79+ }
7280 }
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()?;
73105 }
74106}
75107
@@ -161,6 +193,60 @@ fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
161193 Some((start_byte, end_byte))
162194}
163195
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+
164250#[cfg(test)]
165251mod tests {
166252 use super::*;