pull/13791/merge
Dubrovin E. Iu. 2025-07-24 10:31:09 +02:00 committed by GitHub
commit feb464d527
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 154 additions and 27 deletions

View File

@ -770,15 +770,28 @@ pub fn keep_or_remove_matches(
regex: &rope::Regex, regex: &rope::Regex,
remove: bool, remove: bool,
) -> Option<Selection> { ) -> Option<Selection> {
let prim_range = selection.primary();
let mut diff = usize::MAX;
let mut new_prim = 0;
let result: SmallVec<_> = selection let result: SmallVec<_> = selection
.iter() .iter()
.filter(|range| regex.is_match(text.regex_input_at(range.from()..range.to())) ^ remove) .filter(|range| regex.is_match(text.regex_input_at(range.from()..range.to())) ^ remove)
.enumerate()
.map(|(idx, range)| {
let new_diff = range.head.abs_diff(prim_range.head);
if new_diff < diff {
diff = new_diff;
new_prim = idx;
}
range
})
.copied() .copied()
.collect(); .collect();
// TODO: figure out a new primary index
if !result.is_empty() { if !result.is_empty() {
return Some(Selection::new(result, 0)); return Some(Selection::new(result, new_prim));
} }
None None
} }
@ -789,45 +802,61 @@ pub fn select_on_matches(
selection: &Selection, selection: &Selection,
regex: &rope::Regex, regex: &rope::Regex,
) -> Option<Selection> { ) -> Option<Selection> {
let mut result = SmallVec::with_capacity(selection.len());
let prim_range = selection.primary();
let mut diff = usize::MAX;
let mut new_prim = 0;
let mut result = SmallVec::with_capacity(selection.len());
for sel in selection { for sel in selection {
for mat in regex.find_iter(text.regex_input_at(sel.from()..sel.to())) { for mat in regex.find_iter(text.regex_input_at(sel.from()..sel.to())) {
// TODO: retain range direction
let start = text.byte_to_char(mat.start()); let start = text.byte_to_char(mat.start());
let end = text.byte_to_char(mat.end()); let end = text.byte_to_char(mat.end());
let range = Range::new(start, end); let range = match sel.direction() {
Direction::Forward => Range::new(start, end),
Direction::Backward => Range::new(end, start)
};
// Make sure the match is not right outside of the selection. // Make sure the match is not right outside of the selection.
// These invalid matches can come from using RegEx anchors like `^`, `$` // These invalid matches can come from using RegEx anchors like `^`, `$`
if range != Range::point(sel.to()) { if range != Range::point(sel.to()) {
let new_diff = range.head.abs_diff(prim_range.head);
if new_diff < diff {
diff = new_diff;
new_prim = result.len();
}
result.push(range); result.push(range);
} }
} }
} }
// TODO: figure out a new primary index
if !result.is_empty() { if !result.is_empty() {
return Some(Selection::new(result, 0)); return Some(Selection::new(result, new_prim));
} }
None None
} }
pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection { pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection {
let mut result = SmallVec::with_capacity(selection.len());
let mut new_prim = selection.primary_index();
let mut result = SmallVec::with_capacity(selection.len());
for sel in selection { for sel in selection {
let is_prim = *sel == selection.primary();
// Special case: zero-width selection. // Special case: zero-width selection.
if sel.from() == sel.to() { if sel.from() == sel.to() {
if is_prim { new_prim = result.len() }
result.push(*sel); result.push(*sel);
continue; continue;
} }
let saved_len = result.len();
let sel_start = sel.from(); let sel_start = sel.from();
let sel_end = sel.to(); let sel_end = sel.to();
let mut start = sel_start; let mut start = sel_start;
for line in sel.slice(text).lines() { for line in sel.slice(text).lines() {
@ -835,48 +864,70 @@ pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection {
break; break;
}; };
let line_end = start + line.len_chars(); let line_end = start + line.len_chars();
// TODO: retain range direction let range = Range::new(start, line_end - line_ending.len_chars());
result.push(Range::new(start, line_end - line_ending.len_chars())); result.push(
if sel.direction() == Direction::Backward { range.flip() }
else { range }
);
start = line_end; start = line_end;
} }
if start < sel_end { if start < sel_end {
result.push(Range::new(start, sel_end)); result.push(Range::new(start, sel_end));
} }
if is_prim {
new_prim = if sel.head > sel.anchor {
result.len() - 1
} else { saved_len };
}
} }
// TODO: figure out a new primary index Selection::new(result, new_prim)
Selection::new(result, 0)
} }
pub fn split_on_matches(text: RopeSlice, selection: &Selection, regex: &rope::Regex) -> Selection { pub fn split_on_matches(text: RopeSlice, selection: &Selection, regex: &rope::Regex) -> Selection {
let mut result = SmallVec::with_capacity(selection.len());
let mut new_prim = selection.primary_index();
let mut result = SmallVec::with_capacity(selection.len());
for sel in selection { for sel in selection {
let is_prim = *sel == selection.primary();
// Special case: zero-width selection. // Special case: zero-width selection.
if sel.from() == sel.to() { if sel.from() == sel.to() {
if is_prim { new_prim = result.len() }
result.push(*sel); result.push(*sel);
continue; continue;
} }
let saved_len = result.len();
let sel_start = sel.from(); let sel_start = sel.from();
let sel_end = sel.to(); let sel_end = sel.to();
let mut start = sel_start; let mut start = sel_start;
for mat in regex.find_iter(text.regex_input_at(sel_start..sel_end)) { for mat in regex.find_iter(text.regex_input_at(sel_start..sel_end)) {
// TODO: retain range direction
let end = text.byte_to_char(mat.start()); let end = text.byte_to_char(mat.start());
result.push(Range::new(start, end)); let range = Range::new(start, end);
result.push(
if sel.direction() == Direction::Backward { range.flip() }
else { range }
);
start = text.byte_to_char(mat.end()); start = text.byte_to_char(mat.end());
} }
if start < sel_end { if start < sel_end {
result.push(Range::new(start, sel_end)); result.push(Range::new(start, sel_end));
} }
if is_prim && result.len() > saved_len {
new_prim = if sel.head > sel.anchor {
result.len() - 1
} else { saved_len };
}
} }
// TODO: figure out a new primary index Selection::new(result, new_prim)
Selection::new(result, 0)
} }
#[cfg(test)] #[cfg(test)]
@ -1106,7 +1157,7 @@ mod test {
select_on_matches(s, &selection, &rope::Regex::new(r"[A-Z][a-z]*").unwrap()), select_on_matches(s, &selection, &rope::Regex::new(r"[A-Z][a-z]*").unwrap()),
Some(Selection::new( Some(Selection::new(
smallvec![Range::new(0, 6), Range::new(19, 26)], smallvec![Range::new(0, 6), Range::new(19, 26)],
0 1
)) ))
); );
@ -1145,7 +1196,7 @@ mod test {
select_on_matches(s, &Selection::single(0, 6), &start_of_line), select_on_matches(s, &Selection::single(0, 6), &start_of_line),
Some(Selection::new( Some(Selection::new(
smallvec![Range::point(0), Range::point(5)], smallvec![Range::point(0), Range::point(5)],
0 1
)) ))
); );
assert_eq!( assert_eq!(
@ -1165,7 +1216,7 @@ mod test {
), ),
Some(Selection::new( Some(Selection::new(
smallvec![Range::point(12), Range::new(13, 30), Range::new(31, 36)], smallvec![Range::point(12), Range::new(13, 30), Range::new(31, 36)],
0 2
)) ))
); );
} }

View File

@ -1973,12 +1973,88 @@ fn page_cursor_half_down(cx: &mut Context) {
scroll(cx, offset, Direction::Forward, true); scroll(cx, offset, Direction::Forward, true);
} }
#[allow(deprecated)] fn copy_selection_on_visual_line(cx: &mut Context, direction: Direction) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
let text_fmt = doc.text_format(view.inner_area(doc).width, None);
let mut annotations = view.text_annotations(doc, None);
annotations.clear_line_annotations();
let mut primary_idx = selection.primary_index();
let mut new_ranges = SmallVec::with_capacity(selection.len() * (count + 1));
new_ranges.extend_from_slice(selection.ranges());
//copy the selection to the relative line number
let to_relative_line_number = count > 1;
for range in selection.iter() {
let is_primary = *range == selection.primary();
// The range is always head exclusive
let (head, anchor) =
if range.anchor < range.head { (range.head - 1, range.anchor ) }
else { (range.head , range.anchor.saturating_sub(1)) };
let min_idx = std::cmp::min(head, anchor);
let (head_pos , _) = visual_offset_from_block(
text, min_idx, head , &text_fmt, &annotations);
let (anchor_pos, _) = visual_offset_from_block(
text, min_idx, anchor, &text_fmt, &annotations);
let height =
std::cmp::max(head_pos.row, anchor_pos.row)
- std::cmp::min(head_pos.row, anchor_pos.row)
+ 1;
let mut i = 0;
let mut step = 0;
while step < count {
use Direction::*;
i += match direction { Forward => 1, Backward => -1, };
let offset = i * height as isize;
let (new_head , _) = char_idx_at_visual_offset(
text, head , offset, head_pos.col , &text_fmt, &annotations);
let (new_anchor, _) = char_idx_at_visual_offset(
text, anchor, offset, anchor_pos.col, &text_fmt, &annotations);
let (Position { col: new_head_col , ..}, _) = visual_offset_from_block(
text, new_head , new_head , &text_fmt, &annotations);
let (Position { col: new_anchor_col, ..}, _) = visual_offset_from_block(
text, new_anchor, new_anchor, &text_fmt, &annotations);
// check the bottom doc boundary
if new_head >= text.len_chars()
|| new_anchor >= text.len_chars() { break }
// skip lines that are too short
if head_pos.col == new_head_col && anchor_pos.col == new_anchor_col {
new_ranges.push(
Range::point(new_anchor)
.put_cursor(text, new_head, true) );
if is_primary { primary_idx = new_ranges.len() - 1 }
if ! to_relative_line_number { step += 1 }
}
// always increment if `count` > 1
if to_relative_line_number { step += 1 }
// check the top doc boundary
if new_head == 0
&& new_anchor == 0 { break }
} }
drop(annotations);
doc.set_selection(view.id, Selection::new(new_ranges, primary_idx))
}
#[deprecated = "Doesn't account for softwrap or decorations, use copy_selection_on_visual_line instead"]
#[allow(dead_code, deprecated)]
// currently uses the deprecated `visual_coords_at_pos`/`pos_at_visual_coords` functions // currently uses the deprecated `visual_coords_at_pos`/`pos_at_visual_coords` functions
// as this function ignores softwrapping (and virtual text) and instead only cares // as this function ignores softwrapping (and virtual text) and instead only cares
// about "text visual position" // about "text visual position"
//
// TODO: implement a variant of that uses visual lines and respects virtual text
fn copy_selection_on_line(cx: &mut Context, direction: Direction) { fn copy_selection_on_line(cx: &mut Context, direction: Direction) {
use helix_core::{pos_at_visual_coords, visual_coords_at_pos}; use helix_core::{pos_at_visual_coords, visual_coords_at_pos};
@ -2061,11 +2137,11 @@ fn copy_selection_on_line(cx: &mut Context, direction: Direction) {
} }
fn copy_selection_on_prev_line(cx: &mut Context) { fn copy_selection_on_prev_line(cx: &mut Context) {
copy_selection_on_line(cx, Direction::Backward) copy_selection_on_visual_line(cx, Direction::Backward)
} }
fn copy_selection_on_next_line(cx: &mut Context) { fn copy_selection_on_next_line(cx: &mut Context) {
copy_selection_on_line(cx, Direction::Forward) copy_selection_on_visual_line(cx, Direction::Forward)
} }
fn select_all(cx: &mut Context) { fn select_all(cx: &mut Context) {