helix: Fix line selection after undoing delete (#55365)
Closes #55170 Fixes Helix mode undo selection history after `d` deletes the character under an empty cursor. Previously, `d` temporarily expanded the cursor to a one-character selection before deleting, and undo restored that temporary selection. When the deleted character was a newline, pressing `x` after undo selected additional line which was wrong. This preserves the original Helix selection as the undo-restored selection while keeping the existing delete behavior unchanged. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed Helix mode selecting one extra line after undoing a newline delete Co-authored-by: Tom Houlé <13155277+tomhoule@users.noreply.github.com>
This commit is contained in:
1 parent
8f950dbb7b
commit
dd0c77bc5b
3 files changed
+28
-4
No files matched your search
@@ -2829,6 +2829,10 @@ mod test {
|
||||
ˇ»line five"},
|
||||
Mode::HelixNormal,
|
||||
);
|
||||
|
||||
cx.set_state("oneˇ\ntwo\nthree", Mode::HelixNormal);
|
||||
cx.simulate_keystrokes("d u x");
|
||||
cx.assert_state("«one\nˇ»two\nthree", Mode::HelixNormal);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -146,6 +146,8 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
|
||||
Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| {
|
||||
vim.record_current_action(cx);
|
||||
let original_selections =
|
||||
vim.update_editor(cx, |_, editor, _| editor.selections.disjoint_anchors_arc());
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
@@ -155,7 +157,17 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
})
|
||||
})
|
||||
});
|
||||
vim.visual_delete(false, window, cx);
|
||||
let transaction_id = vim.visual_delete(false, window, cx);
|
||||
if let (Some(original_selections), Some(transaction_id)) =
|
||||
(original_selections, transaction_id)
|
||||
{
|
||||
let updated = vim.update_editor(cx, |_, editor, _| {
|
||||
editor.modify_transaction_selection_history(transaction_id, |selections| {
|
||||
selections.0 = original_selections;
|
||||
})
|
||||
});
|
||||
debug_assert_ne!(updated, Some(false));
|
||||
}
|
||||
vim.switch_mode(Mode::HelixNormal, true, window, cx);
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use gpui::{Context, Window, actions};
|
||||
use language::{Point, Selection, SelectionGoal};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use search::BufferSearchBar;
|
||||
use text::TransactionId;
|
||||
use util::ResultExt;
|
||||
use workspace::searchable::Direction;
|
||||
|
||||
@@ -625,9 +626,14 @@ impl Vim {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn visual_delete(&mut self, line_mode: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub fn visual_delete(
|
||||
&mut self,
|
||||
line_mode: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<TransactionId> {
|
||||
self.store_visual_marks(window, cx);
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let transaction_id = self.update_editor(cx, |vim, editor, cx| {
|
||||
let mut original_columns: HashMap<_, _> = Default::default();
|
||||
let line_mode = line_mode || editor.selections.line_mode();
|
||||
editor.selections.set_line_mode(false);
|
||||
@@ -707,7 +713,9 @@ impl Vim {
|
||||
});
|
||||
})
|
||||
});
|
||||
let transaction_id = transaction_id.flatten();
|
||||
self.switch_mode(Mode::Normal, true, window, cx);
|
||||
transaction_id
|
||||
}
|
||||
|
||||
pub fn visual_yank(&mut self, line_mode: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -928,7 +936,7 @@ impl Vim {
|
||||
Some(Operator::Change) => self.substitute(None, false, window, cx),
|
||||
Some(Operator::Delete) => {
|
||||
self.stop_recording(cx);
|
||||
self.visual_delete(false, window, cx)
|
||||
self.visual_delete(false, window, cx);
|
||||
}
|
||||
Some(Operator::Yank) => self.visual_yank(false, window, cx),
|
||||
_ => {} // Ignoring other operators
|
||||
|
||||
Reference in new issue
Block a user