feat: toggle diff file review with c in the file picker

This commit is contained in:
Valdemar Lorenzen
2026-10-13 13:17:06 +02:00
parent 38fcf86195
commit 8f87b8d3d1
3 changed files with 45 additions and 2 deletions
+1 -1
View File
@@ -249,7 +249,7 @@ The diff's changed-file explorer and floating picker use these same tree and sel
Ordinary `config.core.file_picker` and `config.core.file_explorer` bindings work inside comparisons. Ctrl+w closes a focused picker/explorer, retaining its query, selection, and expansion state; the panes use the reclaimed width. Ctrl+e has no reserved diff behavior. Escape closes the revision selector in either stage, but an opened comparison still closes explicitly through `:diff.close`, its palette action, or the generated action value below. `config.core.diff` remains the opening action and exposes immutable `close`, `mode.toggle`, `view.toggle`, and `orientation.toggle` children. New comparisons default to Branch changes (merge base → selected After); Normal-mode Ctrl+b, `:diff.mode.toggle`, or the palette toggles to direct Snapshots and back, preserving the selected endpoints. Selecting Working checkout on either side forces Snapshots. Ordinary `config.core.file_picker` and `config.core.file_explorer` bindings work inside comparisons. Ctrl+w closes a focused picker/explorer, retaining its query, selection, and expansion state; the panes use the reclaimed width. Ctrl+e has no reserved diff behavior. Escape closes the revision selector in either stage, but an opened comparison still closes explicitly through `:diff.close`, its palette action, or the generated action value below. `config.core.diff` remains the opening action and exposes immutable `close`, `mode.toggle`, `view.toggle`, and `orientation.toggle` children. New comparisons default to Branch changes (merge base → selected After); Normal-mode Ctrl+b, `:diff.mode.toggle`, or the palette toggles to direct Snapshots and back, preserving the selected endpoints. Selecting Working checkout on either side forces Snapshots.
`config.core.diff.review.toggle` is a non-mutating Normal-mode action, also available as `:diff.review.toggle` and in the palette. It marks/unmarks the highlighted changed-file leaf when the comparison's explorer is focused, or the current comparison file from a text pane. Read-only snapshots permit review marks. For example, `config.bind "ctrl+g", config.core.diff.review.toggle`; use the palette or a control-key binding while filtering so printable input stays in the query. No default review shortcut is installed. `config.core.diff.review.toggle` is a non-mutating Normal-mode action, also available as `:diff.review.toggle` and in the palette. It marks/unmarks the highlighted changed-file leaf when the comparison's explorer is focused, or the current comparison file from a text pane. Read-only snapshots permit review marks. The default `c` shortcut applies only while browsing the diff's changed-file tree; explicit bindings and disabled keys override it. For example, `config.bind "ctrl+g", config.core.diff.review.toggle`; use the palette or a control-key binding while filtering, including List view, so printable input stays in the query.
`config.core.pane.close` and `config.core.pane.force_close` close a focused **docked** file list instead of its editor pane, without discarding buffers or exiting the comparison. A floating picker does not intercept these actions: normal close still protects underlying dirty buffers, while force-close retains its existing discard semantics. For example, bind `config.bind "ctrl+x", config.core.pane.close`; focused tree/selection overrides and printable filter input retain priority. Palette-originated close actions retain the originating dock target through force confirmation. `config.core.pane.close` and `config.core.pane.force_close` close a focused **docked** file list instead of its editor pane, without discarding buffers or exiting the comparison. A floating picker does not intercept these actions: normal close still protects underlying dirty buffers, while force-close retains its existing discard semantics. For example, bind `config.bind "ctrl+x", config.core.pane.close`; focused tree/selection overrides and printable filter input retain priority. Palette-originated close actions retain the originating dock target through force confirmation.
+1 -1
View File
@@ -370,7 +370,7 @@ Use the ordinary **file-picker/file-explorer actions and bindings**: Space `o` o
File rows show colored **A** (added, green), **M** (modified, yellow), **D** (removed, red), or **R** (renamed, cyan) markers in Tree and List views, including filtered results. Rename rows show `R old/path -> new/path`; directories have no file-status marker. Ordinary file exploration remains unannotated. File rows show colored **A** (added, green), **M** (modified, yellow), **D** (removed, red), or **R** (renamed, cyan) markers in Tree and List views, including filtered results. Rename rows show `R old/path -> new/path`; directories have no file-status marker. Ordinary file exploration remains unannotated.
Mark or unmark a diff file with **`:diff.review.toggle`** or the command-palette action. With the changed-file explorer focused, it toggles the highlighted file without opening it; from a text pane, it toggles the displayed file. Reviewed files show a green **`✓` immediately before A/M/D/R** in Tree and List views. Directories and ordinary filesystem pickers cannot be marked. There is no default shortcut; a control-key binding is useful while a filter is focused, since printable keys remain filter text. Mark or unmark a diff file with **`c` while browsing the changed-file tree**, **`:diff.review.toggle`**, or the command-palette action. With the changed-file explorer focused, it toggles the highlighted file without opening it; from a text pane, the command toggles the displayed file. Reviewed files show a green **`✓` immediately before A/M/D/R** in Tree and List views. Directories and ordinary filesystem pickers cannot be marked. While filtering, including List view, `c` remains query text; use the palette or a control-key binding there. Explicit bindings take precedence over the default browsing shortcut.
Review marks belong only to the open comparison and survive filtering, file navigation, lazy loading, tabs, and split/unified/orientation changes. A new or recomputed comparison clears them. Editing a Working After file clears only that file's mark; unchanged refreshes and saves preserve it. Marks do not change document text, undo history, or repository state, and are not persisted after closing the comparison. Review marks belong only to the open comparison and survive filtering, file navigation, lazy loading, tabs, and split/unified/orientation changes. A new or recomputed comparison clears them. Editing a Working After file clears only that file's mark; unchanged refreshes and saves preserve it. Marks do not change document text, undo history, or repository state, and are not persisted after closing the comparison.
+43
View File
@@ -3232,6 +3232,22 @@ impl Editor {
if !self.diff_interacting() && self.handle_selection_binding(&input) { if !self.diff_interacting() && self.handle_selection_binding(&input) {
return None; return None;
} }
if files_focused
&& input == Input::Char('c')
&& self
.diff
.as_ref()
.and_then(|diff| diff.files.as_ref())
.is_some_and(|files| files.view == FileView::Tree && !files.searching)
&& input != self.leader
&& !self.has_sequence_prefix(self.binding_mode(), &input)
&& !self
.bindings
.get(&self.binding_mode())
.is_some_and(|keys| keys.contains_key(&input))
{
return self.execute_action(Action::DiffReviewToggle.entry(), "");
}
if self.sequence_pending().is_some() if self.sequence_pending().is_some()
|| (phase == Phase::View || (phase == Phase::View
&& (input == self.leader || self.has_sequence_prefix(self.binding_mode(), &input))) && (input == self.leader || self.has_sequence_prefix(self.binding_mode(), &input)))
@@ -13575,6 +13591,33 @@ mod tests {
assert!(editor.diff().unwrap().reviewed_files.is_empty()); assert!(editor.diff().unwrap().reviewed_files.is_empty());
} }
#[test]
fn diff_review_c_toggles_only_while_browsing_and_respects_overrides() {
let directory = Directory::new();
let mut editor = editor();
historical_diff(
&mut editor,
&directory.0,
vec!["before".into()],
vec!["after".into()],
);
editor.handle(Input::BackTab);
assert_eq!(editor.diff().unwrap().focus, Focus::Files);
editor.handle(Input::Char('c'));
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([0]));
editor.handle(Input::Char('c'));
assert!(editor.diff().unwrap().reviewed_files.is_empty());
editor.handle(Input::Char('i'));
editor.handle(Input::Char('c'));
assert_eq!(editor.diff().unwrap().files.as_ref().unwrap().query, "c");
assert!(editor.diff().unwrap().reviewed_files.is_empty());
editor.handle(Input::Escape);
editor.handle(Input::Ctrl('u'));
editor.bind("normal", "c", None).unwrap();
editor.handle(Input::Char('c'));
assert!(editor.diff().unwrap().reviewed_files.is_empty());
}
#[test] #[test]
fn diff_review_files_action_targets_highlight_without_stealing_filter_text() { fn diff_review_files_action_targets_highlight_without_stealing_filter_text() {
let directory = Directory::new(); let directory = Directory::new();