|
|
@ -1,18 +1,16 @@
|
|
|
|
use std::fmt::Display;
|
|
|
|
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;
|
|
|
|
use ropey::RopeSlice;
|
|
|
|
|
|
|
|
|
|
|
|
pub const PAIRS: &[(char, char)] = &[
|
|
|
|
|
|
|
|
('(', ')'),
|
|
|
|
|
|
|
|
('[', ']'),
|
|
|
|
|
|
|
|
('{', '}'),
|
|
|
|
|
|
|
|
('<', '>'),
|
|
|
|
|
|
|
|
('«', '»'),
|
|
|
|
|
|
|
|
('「', '」'),
|
|
|
|
|
|
|
|
('(', ')'),
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
pub enum Error {
|
|
|
|
pub enum Error {
|
|
|
|
PairNotFound,
|
|
|
|
PairNotFound,
|
|
|
@ -34,32 +32,68 @@ impl Display for Error {
|
|
|
|
|
|
|
|
|
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
|
|
|
|
|
|
|
|
/// Given any char in [PAIRS], return the open and closing chars. If not found in
|
|
|
|
/// Finds the position of surround pairs of any [`crate::match_brackets::PAIRS`]
|
|
|
|
/// [PAIRS] return (ch, ch).
|
|
|
|
/// using tree-sitter when possible.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # Returns
|
|
|
|
/// use helix_core::surround::get_pair;
|
|
|
|
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// assert_eq!(get_pair('['), ('[', ']'));
|
|
|
|
/// Tuple `(anchor, head)`, meaning it is not always ordered.
|
|
|
|
/// assert_eq!(get_pair('}'), ('{', '}'));
|
|
|
|
pub fn find_nth_closest_pairs_pos(
|
|
|
|
/// assert_eq!(get_pair('"'), ('"', '"'));
|
|
|
|
syntax: Option<&Syntax>,
|
|
|
|
/// ```
|
|
|
|
text: RopeSlice,
|
|
|
|
pub fn get_pair(ch: char) -> (char, char) {
|
|
|
|
range: Range,
|
|
|
|
PAIRS
|
|
|
|
skip: usize,
|
|
|
|
.iter()
|
|
|
|
) -> Result<(usize, usize)> {
|
|
|
|
.find(|(open, close)| *open == ch || *close == ch)
|
|
|
|
match syntax {
|
|
|
|
.copied()
|
|
|
|
Some(syntax) => find_nth_closest_pairs_ts(syntax, text, range, skip),
|
|
|
|
.unwrap_or((ch, ch))
|
|
|
|
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,
|
|
|
|
text: RopeSlice,
|
|
|
|
range: Range,
|
|
|
|
range: Range,
|
|
|
|
mut skip: usize,
|
|
|
|
mut skip: usize,
|
|
|
|
) -> Result<(usize, usize)> {
|
|
|
|
) -> Result<(usize, usize)> {
|
|
|
|
let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch);
|
|
|
|
let mut opening = range.from();
|
|
|
|
let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch);
|
|
|
|
// 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 mut stack = Vec::with_capacity(2);
|
|
|
|
let pos = range.from();
|
|
|
|
let pos = range.from();
|
|
|
|
let mut close_pos = pos.saturating_sub(1);
|
|
|
|
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) {
|
|
|
|
for ch in text.chars_at(pos) {
|
|
|
|
close_pos += 1;
|
|
|
|
close_pos += 1;
|
|
|
|
|
|
|
|
|
|
|
|
if is_open_pair(ch) {
|
|
|
|
if is_open_bracket(ch) {
|
|
|
|
// Track open pairs encountered so that we can step over
|
|
|
|
// Track open pairs encountered so that we can step over
|
|
|
|
// the corresponding close pairs that will come up further
|
|
|
|
// the corresponding close pairs that will come up further
|
|
|
|
// down the loop. We want to find a lone close pair whose
|
|
|
|
// down the loop. We want to find a lone close pair whose
|
|
|
@ -76,7 +110,7 @@ pub fn find_nth_closest_pairs_pos(
|
|
|
|
continue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !is_close_pair(ch) {
|
|
|
|
if !is_close_bracket(ch) {
|
|
|
|
// We don't care if this character isn't a brace pair item,
|
|
|
|
// We don't care if this character isn't a brace pair item,
|
|
|
|
// so short circuit here.
|
|
|
|
// so short circuit here.
|
|
|
|
continue;
|
|
|
|
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(
|
|
|
|
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
|
|
|
|
/// are automatically detected around each cursor (note that this may result
|
|
|
|
/// in them selecting different surround characters for each selection).
|
|
|
|
/// in them selecting different surround characters for each selection).
|
|
|
|
pub fn get_surround_pos(
|
|
|
|
pub fn get_surround_pos(
|
|
|
|
|
|
|
|
syntax: Option<&Syntax>,
|
|
|
|
text: RopeSlice,
|
|
|
|
text: RopeSlice,
|
|
|
|
selection: &Selection,
|
|
|
|
selection: &Selection,
|
|
|
|
ch: Option<char>,
|
|
|
|
ch: Option<char>,
|
|
|
@ -257,9 +296,13 @@ pub fn get_surround_pos(
|
|
|
|
let mut change_pos = Vec::new();
|
|
|
|
let mut change_pos = Vec::new();
|
|
|
|
|
|
|
|
|
|
|
|
for &range in selection {
|
|
|
|
for &range in selection {
|
|
|
|
let (open_pos, close_pos) = match ch {
|
|
|
|
let (open_pos, close_pos) = {
|
|
|
|
|
|
|
|
let range_raw = match ch {
|
|
|
|
Some(ch) => find_nth_pairs_pos(text, ch, range, skip)?,
|
|
|
|
Some(ch) => find_nth_pairs_pos(text, ch, range, skip)?,
|
|
|
|
None => find_nth_closest_pairs_pos(text, 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) {
|
|
|
|
if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) {
|
|
|
|
return Err(Error::CursorOverlap);
|
|
|
|
return Err(Error::CursorOverlap);
|
|
|
|