Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/match_brackets.rs')
-rw-r--r--helix-core/src/match_brackets.rs98
1 files changed, 72 insertions, 26 deletions
diff --git a/helix-core/src/match_brackets.rs b/helix-core/src/match_brackets.rs
index b8bcc28c..95d6a3dc 100644
--- a/helix-core/src/match_brackets.rs
+++ b/helix-core/src/match_brackets.rs
@@ -9,16 +9,32 @@ use crate::Syntax;
const MAX_PLAINTEXT_SCAN: usize = 10000;
const MATCH_LIMIT: usize = 16;
-// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
-const PAIRS: &[(char, char)] = &[
+pub const BRACKETS: [(char, char); 7] = [
('(', ')'),
('{', '}'),
('[', ']'),
('<', '>'),
- ('\'', '\''),
- ('\"', '\"'),
+ ('«', '»'),
+ ('「', '」'),
+ ('(', ')'),
];
+// The difference between BRACKETS and PAIRS is that we can find matching
+// BRACKETS in a plain text file, but we can't do the same for PAIRs.
+// PAIRS also contains all BRACKETS.
+pub const PAIRS: [(char, char); BRACKETS.len() + 3] = {
+ let mut pairs = [(' ', ' '); BRACKETS.len() + 3];
+ let mut idx = 0;
+ while idx < BRACKETS.len() {
+ pairs[idx] = BRACKETS[idx];
+ idx += 1;
+ }
+ pairs[idx] = ('"', '"');
+ pairs[idx + 1] = ('\'', '\'');
+ pairs[idx + 2] = ('`', '`');
+ pairs
+};
+
/// Returns the position of the matching bracket under cursor.
///
/// If the cursor is on the opening bracket, the position of
@@ -30,7 +46,7 @@ const PAIRS: &[(char, char)] = &[
/// If no matching bracket is found, `None` is returned.
#[must_use]
pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
- if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
+ if pos >= doc.len_chars() || !is_valid_pair(doc.char(pos)) {
return None;
}
find_pair(syntax, doc, pos, false)
@@ -67,7 +83,7 @@ fn find_pair(
let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
- if is_valid_pair(doc, start_char, end_char) {
+ if is_valid_pair_on_pos(doc, start_char, end_char) {
if end_byte == pos {
return Some(start_char);
}
@@ -140,14 +156,22 @@ fn find_pair(
/// If no matching bracket is found, `None` is returned.
#[must_use]
pub fn find_matching_bracket_plaintext(doc: RopeSlice, cursor_pos: usize) -> Option<usize> {
- // Don't do anything when the cursor is not on top of a bracket.
let bracket = doc.get_char(cursor_pos)?;
+ let matching_bracket = {
+ let pair = get_pair(bracket);
+ if pair.0 == bracket {
+ pair.1
+ } else {
+ pair.0
+ }
+ };
+ // Don't do anything when the cursor is not on top of a bracket.
if !is_valid_bracket(bracket) {
return None;
}
// Determine the direction of the matching.
- let is_fwd = is_forward_bracket(bracket);
+ let is_fwd = is_open_bracket(bracket);
let chars_iter = if is_fwd {
doc.chars_at(cursor_pos + 1)
} else {
@@ -159,19 +183,7 @@ pub fn find_matching_bracket_plaintext(doc: RopeSlice, cursor_pos: usize) -> Opt
for (i, candidate) in chars_iter.take(MAX_PLAINTEXT_SCAN).enumerate() {
if candidate == bracket {
open_cnt += 1;
- } else if is_valid_pair(
- doc,
- if is_fwd {
- cursor_pos
- } else {
- cursor_pos - i - 1
- },
- if is_fwd {
- cursor_pos + i + 1
- } else {
- cursor_pos
- },
- ) {
+ } else if candidate == matching_bracket {
// Return when all pending brackets have been closed.
if open_cnt == 1 {
return Some(if is_fwd {
@@ -187,15 +199,49 @@ pub fn find_matching_bracket_plaintext(doc: RopeSlice, cursor_pos: usize) -> Opt
None
}
-fn is_valid_bracket(c: char) -> bool {
- PAIRS.iter().any(|(l, r)| *l == c || *r == c)
+/// Returns the open and closing chars pair. If not found in
+/// [`BRACKETS`] returns (ch, ch).
+///
+/// ```
+/// use helix_core::match_brackets::get_pair;
+///
+/// assert_eq!(get_pair('['), ('[', ']'));
+/// assert_eq!(get_pair('}'), ('{', '}'));
+/// assert_eq!(get_pair('"'), ('"', '"'));
+/// ```
+pub fn get_pair(ch: char) -> (char, char) {
+ PAIRS
+ .iter()
+ .find(|(open, close)| *open == ch || *close == ch)
+ .copied()
+ .unwrap_or((ch, ch))
+}
+
+pub fn is_open_bracket(ch: char) -> bool {
+ BRACKETS.iter().any(|(l, _)| *l == ch)
+}
+
+pub fn is_close_bracket(ch: char) -> bool {
+ BRACKETS.iter().any(|(_, r)| *r == ch)
+}
+
+pub fn is_valid_bracket(ch: char) -> bool {
+ BRACKETS.iter().any(|(l, r)| *l == ch || *r == ch)
+}
+
+pub fn is_open_pair(ch: char) -> bool {
+ PAIRS.iter().any(|(l, _)| *l == ch)
+}
+
+pub fn is_close_pair(ch: char) -> bool {
+ PAIRS.iter().any(|(_, r)| *r == ch)
}
-fn is_forward_bracket(c: char) -> bool {
- PAIRS.iter().any(|(l, _)| *l == c)
+pub fn is_valid_pair(ch: char) -> bool {
+ PAIRS.iter().any(|(l, r)| *l == ch || *r == ch)
}
-fn is_valid_pair(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
+fn is_valid_pair_on_pos(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
}