mirror of https://github.com/helix-editor/helix
Add fallback command to [ and ] to find in next pair
parent
e9de036c16
commit
3bb88d88c4
|
@ -154,6 +154,12 @@ fn find_nth_closest_pairs_plain(
|
||||||
Err(Error::PairNotFound)
|
Err(Error::PairNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum FindType {
|
||||||
|
Surround(usize),
|
||||||
|
Next(usize),
|
||||||
|
Prev(usize),
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the position of surround pairs of `ch` which can be either a closing
|
/// Find the position of surround pairs of `ch` which can be either a closing
|
||||||
/// or opening pair. `n` will skip n - 1 pairs (eg. n=2 will discard (only)
|
/// or opening pair. `n` will skip n - 1 pairs (eg. n=2 will discard (only)
|
||||||
/// the first pair found and keep looking)
|
/// the first pair found and keep looking)
|
||||||
|
@ -161,7 +167,7 @@ pub fn find_nth_pairs_pos(
|
||||||
text: RopeSlice,
|
text: RopeSlice,
|
||||||
ch: char,
|
ch: char,
|
||||||
range: Range,
|
range: Range,
|
||||||
n: usize,
|
find_type: FindType,
|
||||||
) -> Result<(usize, usize)> {
|
) -> Result<(usize, usize)> {
|
||||||
if text.len_chars() < 2 {
|
if text.len_chars() < 2 {
|
||||||
return Err(Error::PairNotFound);
|
return Err(Error::PairNotFound);
|
||||||
|
@ -172,6 +178,17 @@ pub fn find_nth_pairs_pos(
|
||||||
|
|
||||||
let (open, close) = get_pair(ch);
|
let (open, close) = get_pair(ch);
|
||||||
let pos = range.cursor(text);
|
let pos = range.cursor(text);
|
||||||
|
let (pos, n) = match find_type {
|
||||||
|
FindType::Surround(n) => (pos, n),
|
||||||
|
FindType::Next(n) => match search::find_nth_next(text, open, pos, n) {
|
||||||
|
Some(next_pos) => (next_pos + 1, 1),
|
||||||
|
None => return Err(Error::PairNotFound),
|
||||||
|
},
|
||||||
|
FindType::Prev(n) => match search::find_nth_prev(text, close, pos, n) {
|
||||||
|
Some(next_pos) => (next_pos - 1, 1),
|
||||||
|
None => return Err(Error::PairNotFound),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let (open, close) = if open == close {
|
let (open, close) = if open == close {
|
||||||
if Some(open) == text.get_char(pos) {
|
if Some(open) == text.get_char(pos) {
|
||||||
|
@ -298,7 +315,7 @@ pub fn get_surround_pos(
|
||||||
for &range in selection {
|
for &range in selection {
|
||||||
let (open_pos, close_pos) = {
|
let (open_pos, close_pos) = {
|
||||||
let range_raw = match ch {
|
let range_raw = match ch {
|
||||||
Some(ch) => find_nth_pairs_pos(text, ch, range, skip)?,
|
Some(ch) => find_nth_pairs_pos(text, ch, range, FindType::Surround(skip))?,
|
||||||
None => find_nth_closest_pairs_pos(syntax, text, range, skip)?,
|
None => find_nth_closest_pairs_pos(syntax, text, range, skip)?,
|
||||||
};
|
};
|
||||||
let range = Range::new(range_raw.0, range_raw.1);
|
let range = Range::new(range_raw.0, range_raw.1);
|
||||||
|
@ -392,8 +409,13 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(2, expectations.len());
|
assert_eq!(2, expectations.len());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1)
|
find_nth_pairs_pos(
|
||||||
.expect("find should succeed"),
|
doc.slice(..),
|
||||||
|
'\'',
|
||||||
|
selection.primary(),
|
||||||
|
FindType::Surround(1)
|
||||||
|
)
|
||||||
|
.expect("find should succeed"),
|
||||||
(expectations[0], expectations[1])
|
(expectations[0], expectations[1])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -409,7 +431,46 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(2, expectations.len());
|
assert_eq!(2, expectations.len());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 2)
|
find_nth_pairs_pos(
|
||||||
|
doc.slice(..),
|
||||||
|
'\'',
|
||||||
|
selection.primary(),
|
||||||
|
FindType::Surround(2)
|
||||||
|
)
|
||||||
|
.expect("find should succeed"),
|
||||||
|
(expectations[0], expectations[1])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_inside_third_next_quote() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let (doc, selection, expectations) =
|
||||||
|
rope_with_selections_and_expectations(
|
||||||
|
"some 'nested 'quoted' text' on this 'line'\n'and this one'",
|
||||||
|
" ^ _ _ \n "
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(2, expectations.len());
|
||||||
|
assert_eq!(
|
||||||
|
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), FindType::Next(3))
|
||||||
|
.expect("find should succeed"),
|
||||||
|
(expectations[0], expectations[1])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_inside_prev_quote() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let (doc, selection, expectations) =
|
||||||
|
rope_with_selections_and_expectations(
|
||||||
|
"some 'nested 'quoted' text' on this 'line'\n'and this one'",
|
||||||
|
" _ _ ^ \n "
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(2, expectations.len());
|
||||||
|
assert_eq!(
|
||||||
|
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), FindType::Prev(1))
|
||||||
.expect("find should succeed"),
|
.expect("find should succeed"),
|
||||||
(expectations[0], expectations[1])
|
(expectations[0], expectations[1])
|
||||||
)
|
)
|
||||||
|
@ -425,7 +486,12 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1),
|
find_nth_pairs_pos(
|
||||||
|
doc.slice(..),
|
||||||
|
'\'',
|
||||||
|
selection.primary(),
|
||||||
|
FindType::Surround(1)
|
||||||
|
),
|
||||||
Err(Error::CursorOnAmbiguousPair)
|
Err(Error::CursorOnAmbiguousPair)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::chars::{categorize_char, char_is_whitespace, CharCategory};
|
||||||
use crate::graphemes::{next_grapheme_boundary, prev_grapheme_boundary};
|
use crate::graphemes::{next_grapheme_boundary, prev_grapheme_boundary};
|
||||||
use crate::line_ending::rope_is_line_ending;
|
use crate::line_ending::rope_is_line_ending;
|
||||||
use crate::movement::Direction;
|
use crate::movement::Direction;
|
||||||
|
use crate::surround::FindType;
|
||||||
use crate::syntax::LanguageConfiguration;
|
use crate::syntax::LanguageConfiguration;
|
||||||
use crate::Range;
|
use crate::Range;
|
||||||
use crate::{surround, Syntax};
|
use crate::{surround, Syntax};
|
||||||
|
@ -204,9 +205,15 @@ pub fn textobject_pair_surround(
|
||||||
range: Range,
|
range: Range,
|
||||||
textobject: TextObject,
|
textobject: TextObject,
|
||||||
ch: char,
|
ch: char,
|
||||||
count: usize,
|
find_type: FindType,
|
||||||
) -> Range {
|
) -> Range {
|
||||||
textobject_pair_surround_impl(syntax, slice, range, textobject, Some(ch), count)
|
textobject_pair_surround_impl(
|
||||||
|
syntax,
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
textobject,
|
||||||
|
FindVariant::Char((ch, find_type)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn textobject_pair_surround_closest(
|
pub fn textobject_pair_surround_closest(
|
||||||
|
@ -216,7 +223,18 @@ pub fn textobject_pair_surround_closest(
|
||||||
textobject: TextObject,
|
textobject: TextObject,
|
||||||
count: usize,
|
count: usize,
|
||||||
) -> Range {
|
) -> Range {
|
||||||
textobject_pair_surround_impl(syntax, slice, range, textobject, None, count)
|
textobject_pair_surround_impl(
|
||||||
|
syntax,
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
textobject,
|
||||||
|
FindVariant::Closest(count),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FindVariant {
|
||||||
|
Char((char, FindType)),
|
||||||
|
Closest(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn textobject_pair_surround_impl(
|
fn textobject_pair_surround_impl(
|
||||||
|
@ -224,12 +242,15 @@ fn textobject_pair_surround_impl(
|
||||||
slice: RopeSlice,
|
slice: RopeSlice,
|
||||||
range: Range,
|
range: Range,
|
||||||
textobject: TextObject,
|
textobject: TextObject,
|
||||||
ch: Option<char>,
|
find_variant: FindVariant,
|
||||||
count: usize,
|
|
||||||
) -> Range {
|
) -> Range {
|
||||||
let pair_pos = match ch {
|
let pair_pos = match find_variant {
|
||||||
Some(ch) => surround::find_nth_pairs_pos(slice, ch, range, count),
|
FindVariant::Char((ch, find_type)) => {
|
||||||
None => surround::find_nth_closest_pairs_pos(syntax, slice, range, count),
|
surround::find_nth_pairs_pos(slice, ch, range, find_type)
|
||||||
|
}
|
||||||
|
FindVariant::Closest(count) => {
|
||||||
|
surround::find_nth_closest_pairs_pos(syntax, slice, range, count)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
pair_pos
|
pair_pos
|
||||||
.map(|(anchor, head)| match textobject {
|
.map(|(anchor, head)| match textobject {
|
||||||
|
@ -576,8 +597,14 @@ mod test {
|
||||||
let slice = doc.slice(..);
|
let slice = doc.slice(..);
|
||||||
for &case in scenario {
|
for &case in scenario {
|
||||||
let (pos, objtype, expected_range, ch, count) = case;
|
let (pos, objtype, expected_range, ch, count) = case;
|
||||||
let result =
|
let result = textobject_pair_surround(
|
||||||
textobject_pair_surround(None, slice, Range::point(pos), objtype, ch, count);
|
None,
|
||||||
|
slice,
|
||||||
|
Range::point(pos),
|
||||||
|
objtype,
|
||||||
|
ch,
|
||||||
|
FindType::Surround(count),
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
expected_range.into(),
|
expected_range.into(),
|
||||||
|
|
|
@ -33,7 +33,8 @@ use helix_core::{
|
||||||
object, pos_at_coords,
|
object, pos_at_coords,
|
||||||
regex::{self, Regex},
|
regex::{self, Regex},
|
||||||
search::{self, CharMatcher},
|
search::{self, CharMatcher},
|
||||||
selection, surround,
|
selection,
|
||||||
|
surround::{self, FindType},
|
||||||
syntax::{BlockCommentToken, LanguageServerFeature},
|
syntax::{BlockCommentToken, LanguageServerFeature},
|
||||||
text_annotations::{Overlay, TextAnnotations},
|
text_annotations::{Overlay, TextAnnotations},
|
||||||
textobject,
|
textobject,
|
||||||
|
@ -759,8 +760,10 @@ impl FallbackCommand {
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
static_fallback_commands!(
|
static_fallback_commands!(
|
||||||
select_textobject_inside_surrounding_pair, "Select inside any character acting as a pair (tree-sitter)",
|
select_textobject_inside_surrounding_pair, "Select inside any character pair (tree-sitter)",
|
||||||
select_textobject_around_surrounding_pair, "Select around any character acting as a pair (tree-sitter)",
|
select_textobject_around_surrounding_pair, "Select around any character pair (tree-sitter)",
|
||||||
|
select_textobject_inside_prev_pair, "Select inside previous character pair (tree-sitter)",
|
||||||
|
select_textobject_inside_next_pair, "Select inside next character pair (tree-sitter)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6100,17 +6103,36 @@ fn textobject_change(cx: &mut Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_textobject_inside_surrounding_pair(cx: &mut Context, ch: char) {
|
fn select_textobject_inside_surrounding_pair(cx: &mut Context, ch: char) {
|
||||||
textobject_surrounding_pair(cx, textobject::TextObject::Inside, ch);
|
textobject_surrounding_pair(cx, textobject::TextObject::Inside, ch, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_textobject_around_surrounding_pair(cx: &mut Context, ch: char) {
|
fn select_textobject_around_surrounding_pair(cx: &mut Context, ch: char) {
|
||||||
textobject_surrounding_pair(cx, textobject::TextObject::Around, ch);
|
textobject_surrounding_pair(cx, textobject::TextObject::Around, ch, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_prev_pair(cx: &mut Context, ch: char) {
|
||||||
|
textobject_surrounding_pair(
|
||||||
|
cx,
|
||||||
|
textobject::TextObject::Inside,
|
||||||
|
ch,
|
||||||
|
Some(Direction::Backward),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_next_pair(cx: &mut Context, ch: char) {
|
||||||
|
textobject_surrounding_pair(
|
||||||
|
cx,
|
||||||
|
textobject::TextObject::Inside,
|
||||||
|
ch,
|
||||||
|
Some(Direction::Forward),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn textobject_surrounding_pair(
|
fn textobject_surrounding_pair(
|
||||||
cx: &mut Context,
|
cx: &mut Context,
|
||||||
textobject: textobject::TextObject,
|
textobject: textobject::TextObject,
|
||||||
pair_char: char,
|
pair_char: char,
|
||||||
|
direction: Option<Direction>,
|
||||||
) {
|
) {
|
||||||
if pair_char.is_ascii_alphanumeric() {
|
if pair_char.is_ascii_alphanumeric() {
|
||||||
return;
|
return;
|
||||||
|
@ -6122,7 +6144,18 @@ fn textobject_surrounding_pair(
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
let syntax = doc.syntax();
|
let syntax = doc.syntax();
|
||||||
let selection = doc.selection(view.id).clone().transform(|range| {
|
let selection = doc.selection(view.id).clone().transform(|range| {
|
||||||
textobject::textobject_pair_surround(syntax, text, range, textobject, pair_char, count)
|
let find_type = match direction {
|
||||||
|
None => FindType::Surround,
|
||||||
|
Some(Direction::Forward) => FindType::Next,
|
||||||
|
Some(Direction::Backward) => FindType::Prev,
|
||||||
|
}(count);
|
||||||
|
let mut range = textobject::textobject_pair_surround(
|
||||||
|
syntax, text, range, textobject, pair_char, find_type,
|
||||||
|
);
|
||||||
|
if let Some(direction) = direction {
|
||||||
|
range = range.with_direction(direction);
|
||||||
|
}
|
||||||
|
range
|
||||||
});
|
});
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
};
|
};
|
||||||
|
|
|
@ -131,7 +131,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||||
"g" => select_textobject_around_change,
|
"g" => select_textobject_around_change,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"[" => { "Left bracket"
|
"[" => { "Left bracket" fallback=select_textobject_inside_prev_pair
|
||||||
"d" => goto_prev_diag,
|
"d" => goto_prev_diag,
|
||||||
"D" => goto_first_diag,
|
"D" => goto_first_diag,
|
||||||
"g" => goto_prev_change,
|
"g" => goto_prev_change,
|
||||||
|
@ -145,7 +145,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||||
"p" => goto_prev_paragraph,
|
"p" => goto_prev_paragraph,
|
||||||
"space" => add_newline_above,
|
"space" => add_newline_above,
|
||||||
},
|
},
|
||||||
"]" => { "Right bracket"
|
"]" => { "Right bracket" fallback=select_textobject_inside_next_pair
|
||||||
"d" => goto_next_diag,
|
"d" => goto_next_diag,
|
||||||
"D" => goto_last_diag,
|
"D" => goto_last_diag,
|
||||||
"g" => goto_next_change,
|
"g" => goto_next_change,
|
||||||
|
|
Loading…
Reference in New Issue