feat: mark diff files as reviewed

This commit is contained in:
Valdemar Lorenzen
2026-10-13 13:07:02 +02:00
parent c912abaf17
commit 38fcf86195
6 changed files with 522 additions and 38 deletions
+186 -2
View File
@@ -766,6 +766,12 @@ core_actions![
&[Mode::Normal],
true,
),
(
"core.diff.review.toggle",
Action::DiffReviewToggle,
&[Mode::Normal],
true,
),
(
"core.word_wrap.toggle",
Action::WordWrapToggle,
@@ -3246,6 +3252,7 @@ impl Editor {
| Action::DiffModeToggle
| Action::DiffViewToggle
| Action::DiffOrientationToggle
| Action::DiffReviewToggle
| Action::WordWrapToggle
| Action::GitBlameToggle
| Action::Quit
@@ -6185,6 +6192,7 @@ impl Editor {
| Action::DiffModeToggle
| Action::DiffViewToggle
| Action::DiffOrientationToggle
| Action::DiffReviewToggle
| Action::WordWrapToggle
| Action::GitBlameToggle
| Action::LspDefinition
@@ -7002,13 +7010,25 @@ impl Editor {
}
if matches!(
entry.action,
Action::DiffModeToggle | Action::DiffViewToggle | Action::DiffOrientationToggle
Action::DiffModeToggle
| Action::DiffViewToggle
| Action::DiffOrientationToggle
| Action::DiffReviewToggle
) {
let name = entry.ex.expect("diff display action has an Ex command");
let name = entry.ex.expect("diff action has an Ex command");
if !args.is_empty() {
self.set_message(format!("The {name} command takes no arguments"));
return None;
}
if entry.action == Action::DiffReviewToggle
&& self
.file_picker
.as_ref()
.is_some_and(|picker| picker.focused)
{
self.set_message("Select a file in the comparison");
return None;
}
if let Some(owner) = self.diff_owner.filter(|owner| *owner != self.pane_id) {
return self.with_pane(owner, |editor| editor.execute_action(entry, args));
}
@@ -7036,6 +7056,14 @@ impl Editor {
self.set_message("No comparison is open");
return None;
};
if entry.action == Action::DiffReviewToggle {
match diff.toggle_reviewed() {
Ok(true) => self.set_message("File marked reviewed"),
Ok(false) => self.set_message("File marked unreviewed"),
Err(error) => self.set_message(error),
}
return None;
}
if entry.action == Action::DiffViewToggle {
diff.set_view_mode(match diff.view_mode {
ViewMode::Split => ViewMode::Unified,
@@ -10285,6 +10313,7 @@ fn atomic_save(
mod tests {
use super::*;
use crate::plugin::CompletionEdit;
use std::collections::BTreeSet;
struct Directory(PathBuf);
impl Directory {
@@ -13479,6 +13508,161 @@ mod tests {
assert_eq!(editor.lines(), ["beta"]);
}
#[test]
fn diff_review_actions_preserve_readonly_text_history_and_parked_tabs() {
let directory = Directory::new();
let mut editor = editor();
editor.execute("diff.review.toggle");
assert_eq!(editor.message(), "No comparison is open");
historical_diff(
&mut editor,
&directory.0,
vec!["before".into(), "second".into()],
vec!["after".into(), "second".into()],
);
editor
.bind("normal", "ctrl+y", Some("core.diff.review.toggle".into()))
.unwrap();
let text = editor.lines().to_vec();
let carets = editor.carets();
let revision = editor.text_revision();
let undo = editor.active.undo.len();
let redo = editor.active.redo.len();
let dirty = editor.dirty();
assert!(editor.mutation_blocked());
editor.handle(Input::Ctrl('y'));
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([0]));
editor.execute("diff.review.toggle unexpected");
assert!(editor.message().contains("takes no arguments"));
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([0]));
editor.handle(Input::Ctrl('p'));
type_text(&mut editor, "diff.review.toggle");
editor.handle(Input::Enter);
assert!(editor.diff().unwrap().reviewed_files.is_empty());
assert_eq!(editor.lines(), text);
assert_eq!(editor.carets(), carets);
assert_eq!(editor.text_revision(), revision);
assert_eq!(editor.active.undo.len(), undo);
assert_eq!(editor.active.redo.len(), redo);
assert_eq!(editor.dirty(), dirty);
assert!(!directory.path("file.txt").exists());
editor.handle(Input::Char('\t'));
assert_eq!(editor.diff().unwrap().focus, Focus::After);
assert!(editor.mutation_blocked());
editor.execute(":diff.review.toggle");
editor.execute("diff.view.toggle");
editor.execute("diff.view.toggle");
editor.execute("diff.orientation.toggle");
let first_tab = editor.tab_id();
editor.execute("tab.new");
historical_diff(
&mut editor,
&directory.0,
vec!["base".into()],
vec!["different".into()],
);
assert!(editor.diff().unwrap().reviewed_files.is_empty());
editor.execute("tab.previous");
assert_eq!(editor.tab_id(), first_tab);
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([0]));
editor.execute("diff.close");
historical_diff(
&mut editor,
&directory.0,
vec!["before".into()],
vec!["after".into()],
);
assert!(editor.diff().unwrap().reviewed_files.is_empty());
}
#[test]
fn diff_review_files_action_targets_highlight_without_stealing_filter_text() {
let directory = Directory::new();
fs::write(directory.path("z.txt"), "z").unwrap();
fs::write(directory.path("a.txt"), "a").unwrap();
let mut editor = editor();
working_diff(&mut editor, &directory.0, &["z.txt", "a.txt"]);
editor
.bind("normal", "ctrl+y", Some("core.diff.review.toggle".into()))
.unwrap();
editor
.bind("normal", "Q", Some("core.diff.review.toggle".into()))
.unwrap();
let files = editor.diff.as_mut().unwrap().files.as_mut().unwrap();
files.set_view(FileView::List);
files.reveal_path(Path::new("a.txt"));
editor.handle(Input::Ctrl('y'));
assert_eq!(editor.diff().unwrap().file, 0);
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([1]));
editor.handle(Input::Char('Q'));
assert_eq!(editor.file_picker().unwrap().query, "Q");
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([1]));
editor.handle(Input::Ctrl('u'));
let files = editor.diff.as_mut().unwrap().files.as_mut().unwrap();
files.set_view(FileView::Tree);
files.start_search();
editor.handle(Input::Char('Q'));
assert_eq!(editor.file_picker().unwrap().query, "Q");
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([1]));
editor.handle(Input::Ctrl('y'));
assert_eq!(editor.message(), "No changed file is selected");
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([1]));
editor.file_picker = Some(
FilePicker::scan(
directory.0.clone(),
FilePresentation::Docked,
FileView::List,
)
.unwrap(),
);
editor.execute("diff.review.toggle");
assert_eq!(editor.message(), "Select a file in the comparison");
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([1]));
}
#[test]
fn working_review_marks_survive_save_and_clear_only_the_edited_file() {
let directory = Directory::new();
let first = directory.path("first.txt");
fs::write(&first, "base\n").unwrap();
fs::write(directory.path("second.txt"), "second").unwrap();
let mut editor = Editor::open(Some(first.clone()), vec![]).unwrap();
working_diff(&mut editor, &directory.0, &["first.txt", "second.txt"]);
editor.execute("diff.review.toggle");
editor
.diff
.as_mut()
.unwrap()
.files
.as_mut()
.unwrap()
.reveal_path(Path::new("second.txt"));
editor.execute("diff.review.toggle");
assert_eq!(
editor.diff().unwrap().reviewed_files,
BTreeSet::from([0, 1])
);
editor.handle(Input::BackTab);
assert!(editor.diff_editing());
editor.execute("w");
assert_eq!(
editor.diff().unwrap().reviewed_files,
BTreeSet::from([0, 1])
);
type_text(&mut editor, "iX");
editor.handle(Input::Escape);
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([1]));
editor.execute("diff.review.toggle");
editor.execute("w");
assert_eq!(
editor.diff().unwrap().reviewed_files,
BTreeSet::from([0, 1])
);
assert_eq!(fs::read_to_string(&first).unwrap(), "Xbase\n");
editor.handle(Input::Char('u'));
assert_eq!(editor.diff().unwrap().reviewed_files, BTreeSet::from([1]));
}
#[test]
fn diff_display_defaults_tabs_and_remaps_preserve_native_snapshot_carets() {
let directory = Directory::new();