From 216b1aec08f687e3e84cc4a973d88c040b0d2df4 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 11 Jan 2023 14:57:40 -0800 Subject: [PATCH] fix replace in normal and visual modes --- crates/editor/src/movement.rs | 23 ++++++++++++++++ crates/vim/src/normal.rs | 50 +++++++++++++++++++++++++++++----- crates/vim/src/visual.rs | 51 ++++++++++++++++++++++------------- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 96b2065823..0ede6186da 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -352,6 +352,29 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range< start..end } +pub fn split_display_range_by_lines( + map: &DisplaySnapshot, + range: Range, +) -> Vec> { + let mut result = Vec::new(); + + let mut start = range.start; + // Loop over all the covered rows until the one containing the range end + for row in range.start.row()..range.end.row() { + let row_end_column = map.line_len(row); + let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left); + if start != end { + result.push(start..end); + } + start = map.clip_point(DisplayPoint::new(row + 1, 0), Bias::Left); + } + + // Add the final range from the start of the last end to the original range end. + result.push(start..range.end); + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d2ab40c772..d6391353cf 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -429,14 +429,42 @@ pub(crate) fn normal_replace(text: &str, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, cx, |s| { - s.move_with(|map, selection| { - *selection.end.column_mut() += 1; - selection.end = map.clip_point(selection.end, Bias::Right); - }); + let (map, display_selections) = editor.selections.all_display(cx); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&map.buffer_snapshot); + start..start + }) + .collect::>(); + + let edits = display_selections + .into_iter() + .map(|selection| { + let mut range = selection.range(); + *range.end.column_mut() += 1; + range.end = map.clip_point(range.end, Bias::Right); + + ( + range.start.to_offset(&map, Bias::Left) + ..range.end.to_offset(&map, Bias::Left), + text, + ) + }) + .collect::>(); + + editor.buffer().update(cx, |buffer, cx| { + buffer.edit(edits, None, cx); }); - editor.insert(text, cx); editor.set_clip_at_line_ends(true, cx); + editor.change_selections(None, cx, |s| { + s.select_anchor_ranges(stable_anchors); + }); }); }); vim.pop_operator(cx) @@ -487,6 +515,16 @@ mod test { .await; } + // #[gpui::test] + // async fn test_enter(cx: &mut gpui::TestAppContext) { + // let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]); + // cx.assert_all(indoc! {" + // ˇThe qˇuick broˇwn + // ˇfox jumps" + // }) + // .await; + // } + #[gpui::test] async fn test_k(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]); diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 3eba8699ef..ac8771f969 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use collections::HashMap; use editor::{ - display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, + display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection, }; use gpui::{actions, MutableAppContext, ViewContext}; use language::{AutoindentMode, SelectionGoal}; @@ -318,32 +318,47 @@ pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { let (display_map, selections) = editor.selections.all_adjusted_display(cx); - let mut new_selections = Vec::new(); - editor.buffer().update(cx, |buffer, cx| { - let mut edits = Vec::new(); - for selection in selections.iter() { - let mut selection = selection.clone(); - if !line && !selection.reversed { - // Head is at the end of the selection. Adjust the end position to - // to include the character under the cursor. - *selection.end.column_mut() = selection.end.column() + 1; - selection.end = display_map.clip_point(selection.end, Bias::Right); - } - let range = selection - .map(|p| p.to_offset(&display_map, Bias::Right)) - .range(); - new_selections.push(range.start..range.start); + // Selections are biased right at the start. So we need to store + // anchors that are biased left so that we can restore the selections + // after the change + let stable_anchors = editor + .selections + .disjoint_anchors() + .into_iter() + .map(|selection| { + let start = selection.start.bias_left(&display_map.buffer_snapshot); + start..start + }) + .collect::>(); + + let mut edits = Vec::new(); + for selection in selections.iter() { + let mut selection = selection.clone(); + if !line && !selection.reversed { + // Head is at the end of the selection. Adjust the end position to + // to include the character under the cursor. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = display_map.clip_point(selection.end, Bias::Right); + } + + for row_range in + movement::split_display_range_by_lines(&display_map, selection.range()) + { + let range = row_range.start.to_offset(&display_map, Bias::Right) + ..row_range.end.to_offset(&display_map, Bias::Right); let text = text.repeat(range.len()); edits.push((range, text)); } + } + editor.buffer().update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); - editor.change_selections(None, cx, |s| s.select_ranges(new_selections)); + editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors)); }); }); - vim.pop_operator(cx) + vim.switch_mode(Mode::Normal, false, cx); }); }