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 | 111 |
1 files changed, 77 insertions, 34 deletions
diff --git a/helix-core/src/surround.rs b/helix-core/src/surround.rs index ed976488..879c2adf 100644 --- a/helix-core/src/surround.rs +++ b/helix-core/src/surround.rs @@ -1,18 +1,16 @@ use std::fmt::Display; -use crate::{movement::Direction, search, Range, Selection}; +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 ropey::RopeSlice; -pub const PAIRS: &[(char, char)] = &[ - ('(', ')'), - ('[', ']'), - ('{', '}'), - ('<', '>'), - ('«', '»'), - ('「', '」'), - ('(', ')'), -]; - #[derive(Debug, PartialEq, Eq)] pub enum Error { PairNotFound, @@ -34,32 +32,68 @@ impl Display for Error { type Result<T> = std::result::Result<T, Error>; -/// Given any char in [PAIRS], return the open and closing chars. If not found in -/// [PAIRS] return (ch, ch). +/// Finds the position of surround pairs of any [`crate::match_brackets::PAIRS`] +/// using tree-sitter when possible. /// -/// ``` -/// use helix_core::surround::get_pair; +/// # Returns /// -/// 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)) +/// 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), + } } -pub fn find_nth_closest_pairs_pos( +fn find_nth_closest_pairs_ts( + syntax: &Syntax, text: RopeSlice, range: Range, mut skip: usize, ) -> Result<(usize, usize)> { - let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch); - let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch); + 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); + } + + // 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); @@ -67,7 +101,7 @@ pub fn find_nth_closest_pairs_pos( for ch in text.chars_at(pos) { close_pos += 1; - if is_open_pair(ch) { + if is_open_bracket(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 @@ -76,7 +110,7 @@ pub fn find_nth_closest_pairs_pos( continue; } - if !is_close_pair(ch) { + if !is_close_bracket(ch) { // We don't care if this character isn't a brace pair item, // so short circuit here. continue; @@ -157,7 +191,11 @@ pub fn find_nth_pairs_pos( ) }; - Option::zip(open, close).ok_or(Error::PairNotFound) + // 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), + } } fn find_nth_open_pair( @@ -249,6 +287,7 @@ 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>, @@ -257,9 +296,13 @@ pub fn get_surround_pos( let mut change_pos = Vec::new(); for &range in selection { - 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)?, + 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()) }; if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) { return Err(Error::CursorOverlap); |