@@ -2,6 +2,9 @@ use tree_sitter::Node;
3use crate::{Rope, Syntax};
3use crate::{Rope, Syntax};
5+const MAX_PLAINTEXT_SCAN: usize = 10000;
7+// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
5const PAIRS: &[(char, char)] = &[
8const PAIRS: &[(char, char)] = &[
6 ('(', ')'),
9 ('(', ')'),
7 ('{', '}'),
10 ('{', '}'),
@@ -11,15 +14,15 @@ const PAIRS: &[(char, char)] = &[
11 ('\"', '\"'),
14 ('\"', '\"'),
14−// limit matching pairs to only ( ) { } [ ] < > ' ' " "
16−// Returns the position of the matching bracket under cursor.
17+/// Returns the position of the matching bracket under cursor.
18−// If the cursor is one the opening bracket, the position of
19+/// If the cursor is on the opening bracket, the position of
19−// the closing bracket is returned. If the cursor in the closing
20+/// the closing bracket is returned. If the cursor on the closing
20−// bracket, the position of the opening bracket is returned.
21+/// bracket, the position of the opening bracket is returned.
22−// If the cursor is not on a bracket, `None` is returned.
23+/// If the cursor is not on a bracket, `None` is returned.
25+/// If no matching bracket is found, `None` is returned.
23#[must_use]
26#[must_use]
24pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
27pub 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 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
@@ -70,10 +73,77 @@ fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) ->
76+/// Returns the position of the matching bracket under cursor.
77+/// This function works on plain text and ignores tree-sitter grammar.
78+/// The search is limited to `MAX_PLAINTEXT_SCAN` characters
80+/// If the cursor is on the opening bracket, the position of
81+/// the closing bracket is returned. If the cursor on the closing
82+/// bracket, the position of the opening bracket is returned.
84+/// If the cursor is not on a bracket, `None` is returned.
86+/// If no matching bracket is found, `None` is returned.
88+pub fn find_matching_bracket_current_line_plaintext(
92+ // Don't do anything when the cursor is not on top of a bracket.
93+ let bracket = doc.char(cursor_pos);
94+ if !is_valid_bracket(bracket) {
98+ // Determine the direction of the matching.
99+ let is_fwd = is_forward_bracket(bracket);
100+ let chars_iter = if is_fwd {
101+ doc.chars_at(cursor_pos + 1)
103+ doc.chars_at(cursor_pos).reversed()
106+ let mut open_cnt = 1;
108+ for (i, candidate) in chars_iter.take(MAX_PLAINTEXT_SCAN).enumerate() {
109+ if candidate == bracket {
111+ } else if is_valid_pair(
124+ // Return when all pending brackets have been closed.
126+ return Some(if is_fwd {
73fn is_valid_bracket(c: char) -> bool {
139fn is_valid_bracket(c: char) -> bool {
74 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
140 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
143+fn is_forward_bracket(c: char) -> bool {
144+ PAIRS.iter().any(|(l, _)| *l == c)
77fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
147fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
78 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
148 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
@@ -90,3 +160,36 @@ fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
91 Some((start_byte, end_byte))
161 Some((start_byte, end_byte))
169+ fn test_find_matching_bracket_current_line_plaintext() {
170+ let assert = |input: &str, pos, expected| {
171+ let input = &Rope::from(input);
172+ let actual = find_matching_bracket_current_line_plaintext(input, pos);
173+ assert_eq!(expected, actual.unwrap());
175+ let actual = find_matching_bracket_current_line_plaintext(input, expected);
176+ assert_eq!(pos, actual.unwrap(), "expected symmetrical behaviour");
179+ assert("(hello)", 0, 6);
180+ assert("((hello))", 0, 8);
181+ assert("((hello))", 1, 7);
182+ assert("(((hello)))", 2, 8);
184+ assert("key: ${value}", 6, 12);
185+ assert("key: ${value} # (some comment)", 16, 29);
187+ assert("(paren (paren {bracket}))", 0, 24);
188+ assert("(paren (paren {bracket}))", 7, 23);
189+ assert("(paren (paren {bracket}))", 14, 22);
191+ assert("(prev line\n ) (middle) ( \n next line)", 0, 12);
192+ assert("(prev line\n ) (middle) ( \n next line)", 14, 21);
193+ assert("(prev line\n ) (middle) ( \n next line)", 23, 36);