@@ -1,3 +1,5 @@
1use crate::{Rope, Syntax};
3use crate::{Rope, Syntax};
3const PAIRS: &[(char, char)] = &[
5const PAIRS: &[(char, char)] = &[
@@ -6,50 +8,85 @@ const PAIRS: &[(char, char)] = &[
6 ('[', ']'),
8 ('[', ']'),
7 ('<', '>'),
9 ('<', '>'),
8 ('\'', '\''),
10 ('\'', '\''),
9− ('"', '"'),
11+ ('\"', '\"'),
11// limit matching pairs to only ( ) { } [ ] < >
14// limit matching pairs to only ( ) { } [ ] < >
16+// Returns the position of the matching bracket under cursor.
18+// If the cursor is one the opening bracket, the position of
19+// the closing bracket is returned. If the cursor in the closing
20+// bracket, the position of the opening bracket is returned.
22+// If the cursor is not on a bracket, `None` is returned.
24+pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
25+ if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
28+ find_pair(syntax, doc, pos, false)
31+// Returns the position of the bracket that is closing the current scope.
33+// If the cursor is on an opening or closing bracket, the function
34+// behaves equivalent to [`find_matching_bracket`].
36+// If the cursor position is within a scope, the function searches
37+// for the surrounding scope that is surrounded by brackets and
38+// returns the position of the closing bracket for that scope.
40+// If no surrounding scope is found, the function returns `None`.
13#[must_use]
41#[must_use]
14−pub fn find(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
42+pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
43+ find_pair(syntax, doc, pos, true)
46+fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option<usize> {
15 let tree = syntax.tree();
47 let tree = syntax.tree();
48+ let pos = doc.char_to_byte(pos);
17− let byte_pos = doc.char_to_byte(pos);
50+ let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
19− // most naive implementation: find the innermost syntax node, if we're at the edge of a node,
52+ loop {
20− // return the other edge.
53+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
54+ let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
22− let node = match tree
56+ if is_valid_pair(doc, start_char, end_char) {
23− .root_node()
57+ if end_byte == pos {
24− .named_descendant_for_byte_range(byte_pos, byte_pos)
58+ return Some(start_char);
26− Some(node) => node,
60+ // We return the end char if the cursor is either on the start char
27− None => return None,
61+ // or at some arbitrary position between start and end char.
62+ return Some(end_char);
30− if node.is_error() {
65+ if traverse_parents {
66+ node = node.parent()?;
31− return None;
68+ return None;
73+fn is_valid_bracket(c: char) -> bool {
74+ PAIRS.iter().any(|(l, r)| *l == c || *r == c)
77+fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
78+ PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
81+fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
34 let len = doc.len_bytes();
82 let len = doc.len_bytes();
35 let start_byte = node.start_byte();
84 let start_byte = node.start_byte();
36− let end_byte = node.end_byte().saturating_sub(1); // it's end exclusive
85+ let end_byte = node.end_byte().saturating_sub(1);
37 if start_byte >= len || end_byte >= len {
87 if start_byte >= len || end_byte >= len {
38 return None;
88 return None;
41− let start_char = doc.byte_to_char(start_byte);
42− let end_char = doc.byte_to_char(end_byte);
44− if PAIRS.contains(&(doc.char(start_char), doc.char(end_char))) {
45− if start_byte == byte_pos {
46− return Some(end_char);
49− if end_byte == byte_pos {
91+ Some((start_byte, end_byte))
50− return Some(start_char);