From 8f87b8d3d1a4887d07bdbfa9407ae94b078b1c2a Mon Sep 17 00:00:00 2001 From: Valdemar Lorenzen Date: Tue, 13 Oct 2026 13:17:06 +0200 Subject: [PATCH] feat: toggle diff file review with c in the file picker --- docs/configuration.md | 2 +- docs/editing.md | 2 +- src/editor.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 746aec5..bbf4c93 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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. -`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. diff --git a/docs/editing.md b/docs/editing.md index f46e85f..bc36667 100644 --- a/docs/editing.md +++ b/docs/editing.md @@ -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. -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. diff --git a/src/editor.rs b/src/editor.rs index e3262b6..ede13b5 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -3232,6 +3232,22 @@ impl Editor { if !self.diff_interacting() && self.handle_selection_binding(&input) { 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() || (phase == Phase::View && (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()); } + #[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] fn diff_review_files_action_targets_highlight_without_stealing_filter_text() { let directory = Directory::new();