Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/surround.rs')
| -rw-r--r-- | helix-core/src/surround.rs | 141 |
1 files changed, 39 insertions, 102 deletions
diff --git a/helix-core/src/surround.rs b/helix-core/src/surround.rs index e45346c9..b96cce5a 100644 --- a/helix-core/src/surround.rs +++ b/helix-core/src/surround.rs @@ -1,16 +1,18 @@ use std::fmt::Display; -use crate::{ - graphemes::next_grapheme_boundary, - match_brackets::{ - find_matching_bracket, find_matching_bracket_fuzzy, get_pair, is_close_bracket, - is_open_bracket, - }, - movement::Direction, - search, Range, Selection, Syntax, -}; +use crate::{movement::Direction, search, Range, Selection}; use ropey::RopeSlice; +pub const PAIRS: &[(char, char)] = &[ + ('(', ')'), + ('[', ']'), + ('{', '}'), + ('<', '>'), + ('«', '»'), + ('「', '」'), + ('(', ')'), +]; + #[derive(Debug, PartialEq, Eq)] pub enum Error { PairNotFound, @@ -32,68 +34,32 @@ impl Display for Error { type Result<T> = std::result::Result<T, Error>; -/// Finds the position of surround pairs of any [`crate::match_brackets::PAIRS`] -/// using tree-sitter when possible. +/// Given any char in [PAIRS], return the open and closing chars. If not found in +/// [PAIRS] return (ch, ch). /// -/// # Returns +/// ``` +/// use helix_core::surround::get_pair; /// -/// Tuple `(anchor, head)`, meaning it is not always ordered. -pub fn find_nth_closest_pairs_pos( - syntax: Option<&Syntax>, - text: RopeSlice, - range: Range, - skip: usize, -) -> Result<(usize, usize)> { - match syntax { - Some(syntax) => find_nth_closest_pairs_ts(syntax, text, range, skip), - None => find_nth_closest_pairs_plain(text, range, skip), - } +/// 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)) } -fn find_nth_closest_pairs_ts( - syntax: &Syntax, +pub fn find_nth_closest_pairs_pos( text: RopeSlice, range: Range, mut skip: usize, ) -> Result<(usize, usize)> { - let mut opening = range.from(); - // We want to expand the selection if we are already on the found pair, - // otherwise we would need to subtract "-1" from "range.to()". - let mut closing = range.to(); - - while skip > 0 { - closing = find_matching_bracket_fuzzy(syntax, text, closing).ok_or(Error::PairNotFound)?; - opening = find_matching_bracket(syntax, text, closing).ok_or(Error::PairNotFound)?; - // If we're already on a closing bracket "find_matching_bracket_fuzzy" will return - // the position of the opening bracket. - if closing < opening { - (opening, closing) = (closing, opening); - } + let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch); + let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch); - // In case found brackets are partially inside current selection. - if range.from() < opening || closing < range.to() - 1 { - closing = next_grapheme_boundary(text, closing); - } else { - skip -= 1; - if skip != 0 { - closing = next_grapheme_boundary(text, closing); - } - } - } - - // Keep the original direction. - if let Direction::Forward = range.direction() { - Ok((opening, closing)) - } else { - Ok((closing, opening)) - } -} - -fn find_nth_closest_pairs_plain( - text: RopeSlice, - range: Range, - mut skip: usize, -) -> Result<(usize, usize)> { let mut stack = Vec::with_capacity(2); let pos = range.from(); let mut close_pos = pos.saturating_sub(1); @@ -101,7 +67,7 @@ fn find_nth_closest_pairs_plain( for ch in text.chars_at(pos) { close_pos += 1; - if is_open_bracket(ch) { + if is_open_pair(ch) { // Track open pairs encountered so that we can step over // the corresponding close pairs that will come up further // down the loop. We want to find a lone close pair whose @@ -110,7 +76,7 @@ fn find_nth_closest_pairs_plain( continue; } - if !is_close_bracket(ch) { + if !is_close_pair(ch) { // We don't care if this character isn't a brace pair item, // so short circuit here. continue; @@ -191,11 +157,7 @@ pub fn find_nth_pairs_pos( ) }; - // preserve original direction - match range.direction() { - Direction::Forward => Option::zip(open, close).ok_or(Error::PairNotFound), - Direction::Backward => Option::zip(close, open).ok_or(Error::PairNotFound), - } + Option::zip(open, close).ok_or(Error::PairNotFound) } fn find_nth_open_pair( @@ -205,10 +167,6 @@ fn find_nth_open_pair( mut pos: usize, n: usize, ) -> Option<usize> { - if pos >= text.len_chars() { - return None; - } - let mut chars = text.chars_at(pos + 1); // Adjusts pos for the first iteration, and handles the case of the @@ -287,7 +245,6 @@ fn find_nth_close_pair( /// are automatically detected around each cursor (note that this may result /// in them selecting different surround characters for each selection). pub fn get_surround_pos( - syntax: Option<&Syntax>, text: RopeSlice, selection: &Selection, ch: Option<char>, @@ -296,19 +253,14 @@ pub fn get_surround_pos( let mut change_pos = Vec::new(); for &range in selection { - let (open_pos, close_pos) = { - let range_raw = match ch { - Some(ch) => find_nth_pairs_pos(text, ch, range, skip)?, - None => find_nth_closest_pairs_pos(syntax, text, range, skip)?, - }; - let range = Range::new(range_raw.0, range_raw.1); - (range.from(), range.to()) + let (open_pos, close_pos) = match ch { + Some(ch) => find_nth_pairs_pos(text, ch, range, skip)?, + None => find_nth_closest_pairs_pos(text, range, skip)?, }; if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) { return Err(Error::CursorOverlap); } - // ensure the positions are always paired in the forward direction - change_pos.extend_from_slice(&[open_pos.min(close_pos), close_pos.max(open_pos)]); + change_pos.extend_from_slice(&[open_pos, close_pos]); } Ok(change_pos) } @@ -331,7 +283,7 @@ mod test { ); assert_eq!( - get_surround_pos(None, doc.slice(..), &selection, Some('('), 1).unwrap(), + get_surround_pos(doc.slice(..), &selection, Some('('), 1).unwrap(), expectations ); } @@ -346,7 +298,7 @@ mod test { ); assert_eq!( - get_surround_pos(None, doc.slice(..), &selection, Some('('), 1), + get_surround_pos(doc.slice(..), &selection, Some('('), 1), Err(Error::PairNotFound) ); } @@ -361,7 +313,7 @@ mod test { ); assert_eq!( - get_surround_pos(None, doc.slice(..), &selection, Some('('), 1), + get_surround_pos(doc.slice(..), &selection, Some('('), 1), Err(Error::PairNotFound) // overlapping surround chars ); } @@ -376,7 +328,7 @@ mod test { ); assert_eq!( - get_surround_pos(None, doc.slice(..), &selection, Some('['), 1), + get_surround_pos(doc.slice(..), &selection, Some('['), 1), Err(Error::CursorOverlap) ); } @@ -430,21 +382,6 @@ mod test { ) } - #[test] - fn test_find_nth_closest_pairs_pos_index_range_panic() { - #[rustfmt::skip] - let (doc, selection, _) = - rope_with_selections_and_expectations( - "(a)c)", - "^^^^^" - ); - - assert_eq!( - find_nth_closest_pairs_pos(None, doc.slice(..), selection.primary(), 1), - Err(Error::PairNotFound) - ) - } - // Create a Rope and a matching Selection using a specification language. // ^ is a single-point selection. // _ is an expected index. These are returned as a Vec<usize> for use in assertions. |