fix: preserve Visual selections through command entry
This commit is contained in:
+82
-13
@@ -508,7 +508,11 @@ core_actions![
|
||||
Action::RepeatLastEdit,
|
||||
&[Mode::Normal, Mode::Visual],
|
||||
),
|
||||
("core.command", Action::Command, &[Mode::Normal]),
|
||||
(
|
||||
"core.command",
|
||||
Action::Command,
|
||||
&[Mode::Normal, Mode::Visual]
|
||||
),
|
||||
("core.search", Action::Search, &[Mode::Normal]),
|
||||
("core.search_word", Action::SearchWord, &[Mode::Normal]),
|
||||
(
|
||||
@@ -1350,7 +1354,6 @@ fn default_bindings() -> HashMap<Mode, HashMap<Input, Binding>> {
|
||||
('s', SubstituteCharacter),
|
||||
('u', Undo),
|
||||
('U', Redo),
|
||||
(':', Command),
|
||||
('/', Search),
|
||||
('*', SearchWord),
|
||||
('p', PasteAfter),
|
||||
@@ -1382,6 +1385,7 @@ fn default_bindings() -> HashMap<Mode, HashMap<Input, Binding>> {
|
||||
}
|
||||
for mode in [Mode::Normal, Mode::Visual] {
|
||||
for (key, action) in [
|
||||
(':', Command),
|
||||
('h', Left),
|
||||
('j', Down),
|
||||
('k', Up),
|
||||
@@ -1670,6 +1674,7 @@ struct ViewState {
|
||||
search_highlight: bool,
|
||||
search_match: Option<SearchMatch>,
|
||||
command_line: String,
|
||||
command_from_visual: bool,
|
||||
caret_reveal_revision: u64,
|
||||
lsp_center: Cell<Option<(u64, u64, Cursor)>>,
|
||||
viewport_scroll: Cell<(isize, isize)>,
|
||||
@@ -1691,6 +1696,7 @@ impl ViewState {
|
||||
search_highlight: false,
|
||||
search_match: None,
|
||||
command_line: String::new(),
|
||||
command_from_visual: false,
|
||||
caret_reveal_revision: 0,
|
||||
lsp_center: Cell::new(None),
|
||||
viewport_scroll: Cell::new((0, 0)),
|
||||
@@ -1712,6 +1718,7 @@ impl ViewState {
|
||||
search_highlight: self.search_highlight,
|
||||
search_match: self.search_match,
|
||||
command_line: self.command_line.clone(),
|
||||
command_from_visual: self.command_from_visual,
|
||||
caret_reveal_revision: self.caret_reveal_revision,
|
||||
visual_motions: self.visual_motions.clone(),
|
||||
..Self::empty()
|
||||
@@ -7611,6 +7618,7 @@ impl Editor {
|
||||
self.pane_close_picker_origin = None;
|
||||
self.active.view.command_line = format!("{} ", entry.name);
|
||||
self.palette = None;
|
||||
self.active.view.command_from_visual = self.active.view.mode == Mode::Visual;
|
||||
if self.diff.is_some() && !self.diff_interacting() {
|
||||
self.diff_prompt_mode.get_or_insert(self.active.view.mode);
|
||||
} else if self.active.view.mode == Mode::Insert {
|
||||
@@ -8239,14 +8247,19 @@ impl Editor {
|
||||
None
|
||||
}
|
||||
|
||||
fn finish_diff_prompt(&mut self) {
|
||||
fn finish_prompt(&mut self) {
|
||||
if let Some(mode) = self.diff_prompt_mode.take() {
|
||||
self.active.view.mode = mode;
|
||||
self.active.view.command_line.clear();
|
||||
}
|
||||
if std::mem::take(&mut self.active.view.command_from_visual) {
|
||||
self.active.view.mode = Mode::Visual;
|
||||
self.active.view.command_line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn open_action_prompt(&mut self, entry: &CoreAction) {
|
||||
self.active.view.command_from_visual = self.active.view.mode == Mode::Visual;
|
||||
if self.diff().is_some() && !self.diff_interacting() {
|
||||
self.diff_prompt_mode.get_or_insert(self.active.view.mode);
|
||||
self.palette = None;
|
||||
@@ -8268,8 +8281,10 @@ impl Editor {
|
||||
{
|
||||
self.pane_close_picker_origin = None;
|
||||
}
|
||||
if action == Action::Normal && self.diff_prompt_mode.is_some() {
|
||||
self.finish_diff_prompt();
|
||||
if action == Action::Normal
|
||||
&& (self.diff_prompt_mode.is_some() || self.active.view.command_from_visual)
|
||||
{
|
||||
self.finish_prompt();
|
||||
self.count = None;
|
||||
self.clear_sequence();
|
||||
return None;
|
||||
@@ -8544,6 +8559,8 @@ impl Editor {
|
||||
Action::Redo => self.redo(),
|
||||
Action::RepeatLastEdit => self.repeat_last_edit(),
|
||||
Action::Command | Action::Search => {
|
||||
self.active.view.command_from_visual =
|
||||
action == Action::Command && self.active.view.mode == Mode::Visual;
|
||||
if self.diff().is_some() && !self.diff_interacting() {
|
||||
self.diff_prompt_mode.get_or_insert(self.active.view.mode);
|
||||
}
|
||||
@@ -8978,6 +8995,7 @@ impl Editor {
|
||||
self.normalize_targets();
|
||||
}
|
||||
self.active.view.command_line.clear();
|
||||
self.active.view.command_from_visual = false;
|
||||
self.count = None;
|
||||
self.clear_sequence();
|
||||
self.palette = None;
|
||||
@@ -9252,7 +9270,7 @@ impl Editor {
|
||||
|
||||
pub fn execute(&mut self, command: &str) -> Option<Invocation> {
|
||||
self.pending_lsp_navigation = None;
|
||||
self.finish_diff_prompt();
|
||||
self.finish_prompt();
|
||||
let invocation = self.execute_command(command);
|
||||
self.leave_diff_on_navigation();
|
||||
self.refresh_live_diff();
|
||||
@@ -9278,7 +9296,9 @@ impl Editor {
|
||||
self.active.view.command_line.clear();
|
||||
} else {
|
||||
self.prepare_command();
|
||||
self.finish_command_mode();
|
||||
if self.active.view.mode != Mode::Visual {
|
||||
self.finish_command_mode();
|
||||
}
|
||||
}
|
||||
let Some(feature) = self.owner(name) else {
|
||||
self.set_message(format!("Unknown command: {name}"));
|
||||
@@ -9302,6 +9322,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn finish_command_mode(&mut self) {
|
||||
self.active.view.command_from_visual = false;
|
||||
if self.active.view.mode == Mode::Insert {
|
||||
self.finish_insert();
|
||||
}
|
||||
@@ -9324,7 +9345,7 @@ impl Editor {
|
||||
| Action::ReviewSave
|
||||
| Action::ReviewOpen
|
||||
) {
|
||||
self.finish_diff_prompt();
|
||||
self.finish_prompt();
|
||||
if let Err(error) = self.local_review_command(entry.action, args) {
|
||||
self.set_message(error);
|
||||
}
|
||||
@@ -9408,7 +9429,7 @@ impl Editor {
|
||||
if !matches!(entry.action, Action::PaneClose | Action::PaneForceClose) {
|
||||
self.pane_close_picker_origin = None;
|
||||
}
|
||||
self.finish_diff_prompt();
|
||||
self.finish_prompt();
|
||||
if matches!(
|
||||
entry.action,
|
||||
Action::PaneSplitHorizontal
|
||||
@@ -15928,9 +15949,53 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_review_visual_palette_anchors_both_sides_and_preserves_stale_ranges() {
|
||||
fn visual_commands_preserve_directed_ranges_on_cancel_and_plugin_dispatch() {
|
||||
for shape in ["vll", "vhh", "Vj"] {
|
||||
let mut editor = editor();
|
||||
editor
|
||||
.apply(vec![Effect::Replace(
|
||||
"abcdef\nsecond\nabcdef\nfourth".into(),
|
||||
)])
|
||||
.unwrap();
|
||||
editor.register(plugin("inspect", &["inspect"])).unwrap();
|
||||
set_carets(&mut editor, &[(0, 2), (2, 2)], 1);
|
||||
type_text(&mut editor, shape);
|
||||
let selections = editor.selections();
|
||||
let carets = editor.carets();
|
||||
let linewise = editor.active.view.linewise;
|
||||
type_text(&mut editor, ":inspect");
|
||||
assert_eq!(editor.mode(), Mode::Command);
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.mode(), Mode::Visual);
|
||||
assert_eq!(editor.selections(), selections);
|
||||
assert_eq!(editor.carets(), carets);
|
||||
assert_eq!(editor.active.view.linewise, linewise);
|
||||
editor
|
||||
.bind("visual", ",", Some("core.command".into()))
|
||||
.unwrap();
|
||||
editor.handle(Input::Char(','));
|
||||
type_text(&mut editor, "inspect");
|
||||
editor
|
||||
.handle(Input::Enter)
|
||||
.expect("selection-aware plugin invocation");
|
||||
assert_eq!(editor.mode(), Mode::Visual);
|
||||
assert_eq!(editor.selections(), selections);
|
||||
assert_eq!(editor.carets(), carets);
|
||||
assert_eq!(editor.active.view.linewise, linewise);
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_review_visual_commands_and_palette_preserve_both_side_ranges() {
|
||||
use crate::review::Side;
|
||||
for focus in [Focus::Before, Focus::After] {
|
||||
for (focus, palette) in [
|
||||
(Focus::Before, false),
|
||||
(Focus::After, false),
|
||||
(Focus::Before, true),
|
||||
(Focus::After, true),
|
||||
] {
|
||||
let directory = Directory::new();
|
||||
let root = directory.0.canonicalize().unwrap();
|
||||
let mut editor = editor();
|
||||
@@ -15943,8 +16008,12 @@ mod tests {
|
||||
editor.diff.as_mut().unwrap().focus = focus;
|
||||
editor.bind_diff_buffer();
|
||||
type_text(&mut editor, "Vj");
|
||||
editor.handle(Input::Ctrl('p'));
|
||||
type_text(&mut editor, "diff.comment");
|
||||
if palette {
|
||||
editor.handle(Input::Ctrl('p'));
|
||||
type_text(&mut editor, "diff.comment");
|
||||
} else {
|
||||
type_text(&mut editor, ":diff.comment");
|
||||
}
|
||||
editor.handle(Input::Enter);
|
||||
let diff = editor.workspace_diff().unwrap();
|
||||
let anchor = diff.local_review.document.comments[0].anchor.clone();
|
||||
|
||||
+2
-1
@@ -39,6 +39,7 @@ Normal: i/a insert, I/A insert at first nonblank/end, o/O new line
|
||||
y/d/c/p/P use the system clipboard by default; config.clipboard_mode = \"separate\" isolates it
|
||||
Esc keeps only the primary caret
|
||||
Visual: motions extend selections, y copy, d/x/Delete cut, c change, p/P replace selections
|
||||
: opens command input with the selection preserved; Esc returns to that selection
|
||||
r<char> replaces every selected grapheme, preserving newlines; one undo action
|
||||
I/A insert at each selected line's first nonblank/end
|
||||
Ctrl+l split rows, Ctrl+n add occurrence, Ctrl+s skip, Ctrl+a all
|
||||
@@ -82,7 +83,7 @@ Diff: :diff (Normal); choose Before then After; Working checkout After edits liv
|
||||
Esc closes Before/After selectors; :diff.close exits an opened comparison explicitly
|
||||
Remote refs/history load into temporary comparison repos without changing active refs/checkout
|
||||
Changed files: normal picker/explorer bindings, Ctrl+w close; A/M/D/R status and rename paths
|
||||
Local reviews: :diff.comment at caret; use palette or Visual binding for a selected line range
|
||||
Local reviews: :diff.comment at caret or on a Visual/Visual Line selection
|
||||
:diff.review.edit/delete [ID], :diff.review.next/previous, :diff.review.overview
|
||||
:diff.review.summary; :diff.review.verdict looks_good|changes_requested|none
|
||||
:diff.review.save PATH / :diff.review.open PATH; :w in review panes saves local JSON
|
||||
|
||||
Reference in New Issue
Block a user