feat: mark diff files as reviewed
This commit is contained in:
+136
-34
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::BTreeMap,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt::Write as _,
|
||||
io::{self, Stdout},
|
||||
panic::{self, AssertUnwindSafe, PanicHookInfo},
|
||||
@@ -514,10 +514,11 @@ impl BufferViewports {
|
||||
panel,
|
||||
editor.file_navigation_hint(),
|
||||
&mut self.panels,
|
||||
editor
|
||||
.diff()
|
||||
.and_then(|diff| diff.comparison.as_ref())
|
||||
.map(|comparison| comparison.files.as_slice()),
|
||||
editor.diff().and_then(|diff| {
|
||||
diff.comparison
|
||||
.as_ref()
|
||||
.map(|comparison| (comparison.files.as_slice(), &diff.reviewed_files))
|
||||
}),
|
||||
);
|
||||
if let Some(geometry) = &mut self.panels.diff {
|
||||
geometry.regions[0] = (panel, Focus::Files);
|
||||
@@ -2060,10 +2061,11 @@ fn draw_overlays(
|
||||
panel,
|
||||
editor.file_navigation_hint(),
|
||||
panels,
|
||||
editor
|
||||
.diff()
|
||||
.and_then(|diff| diff.comparison.as_ref())
|
||||
.map(|comparison| comparison.files.as_slice()),
|
||||
editor.diff().and_then(|diff| {
|
||||
diff.comparison
|
||||
.as_ref()
|
||||
.map(|comparison| (comparison.files.as_slice(), &diff.reviewed_files))
|
||||
}),
|
||||
);
|
||||
}
|
||||
if let Some(palette) = editor.palette() {
|
||||
@@ -2774,7 +2776,7 @@ fn draw_diff(
|
||||
navigator,
|
||||
file_navigation_hint,
|
||||
panels,
|
||||
comparison.map(|comparison| comparison.files.as_slice()),
|
||||
comparison.map(|comparison| (comparison.files.as_slice(), &diff.reviewed_files)),
|
||||
);
|
||||
}
|
||||
let mut text_widths = [0; 2];
|
||||
@@ -4128,7 +4130,7 @@ fn draw_file_list(
|
||||
panel: Rect,
|
||||
navigation_hint: &str,
|
||||
panels: &mut WheelPanels,
|
||||
changed_files: Option<&[ChangedFile]>,
|
||||
changed_files: Option<(&[ChangedFile], &BTreeSet<usize>)>,
|
||||
) {
|
||||
if panel.width == 0 || panel.height == 0 {
|
||||
return;
|
||||
@@ -4262,7 +4264,7 @@ fn draw_file_list(
|
||||
}
|
||||
let changed = entry
|
||||
.catalog_index
|
||||
.and_then(|index| changed_files.and_then(|files| files.get(index)));
|
||||
.and_then(|index| changed_files.and_then(|(files, _)| files.get(index)));
|
||||
let prefix = format!(
|
||||
"{} {:indent$}",
|
||||
if selected { ">" } else { " " },
|
||||
@@ -4273,9 +4275,16 @@ fn draw_file_list(
|
||||
0
|
||||
},
|
||||
);
|
||||
let mut spans = Vec::with_capacity(if changed.is_some() { 6 } else { 3 });
|
||||
let mut spans = Vec::with_capacity(if changed.is_some() { 7 } else { 3 });
|
||||
spans.push(Span::raw(prefix));
|
||||
if let Some(file) = changed {
|
||||
let reviewed = entry.catalog_index.is_some_and(|index| {
|
||||
changed_files.is_some_and(|(_, reviewed)| reviewed.contains(&index))
|
||||
});
|
||||
spans.push(Span::styled(
|
||||
if reviewed { "✓ " } else { " " },
|
||||
Style::default().fg(Color::Green),
|
||||
));
|
||||
let color = match file.status {
|
||||
FileStatus::Added => Color::Green,
|
||||
FileStatus::Removed => Color::Red,
|
||||
@@ -5664,6 +5673,7 @@ mod tests {
|
||||
picker.focused = focused;
|
||||
for index in 0..files.len() {
|
||||
picker.reveal_path(&files[index].path);
|
||||
let reviewed = BTreeSet::from([index]);
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_file_list(
|
||||
@@ -5672,15 +5682,15 @@ mod tests {
|
||||
frame.area(),
|
||||
"",
|
||||
&mut WheelPanels::default(),
|
||||
Some(&files),
|
||||
Some((&files, &reviewed)),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
for (file, color) in
|
||||
files
|
||||
.iter()
|
||||
.zip([Color::Green, Color::Yellow, Color::Red, Color::Cyan])
|
||||
for (catalog_index, (file, color)) in files
|
||||
.iter()
|
||||
.zip([Color::Green, Color::Yellow, Color::Red, Color::Cyan])
|
||||
.enumerate()
|
||||
{
|
||||
let path = if view == FileView::Tree {
|
||||
std::path::Path::new(file.path.file_name().unwrap())
|
||||
@@ -5703,6 +5713,30 @@ mod tests {
|
||||
})
|
||||
.unwrap_or_else(|| panic!("missing {expected}"));
|
||||
assert_eq!(buffer[(x, y)].fg, color);
|
||||
assert_eq!(
|
||||
buffer[(x - 2, y)].symbol(),
|
||||
if catalog_index == index { "✓" } else { " " }
|
||||
);
|
||||
assert_eq!(buffer[(x - 1, y)].symbol(), " ");
|
||||
if catalog_index == index {
|
||||
assert_eq!(buffer[(x - 2, y)].fg, Color::Green);
|
||||
assert_eq!(
|
||||
buffer[(x - 2, y)].bg,
|
||||
if focused {
|
||||
Color::Blue
|
||||
} else {
|
||||
Color::DarkGray
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
for row in screen_text(&terminal)
|
||||
.lines()
|
||||
.filter(|row| row.contains("chain/a/"))
|
||||
{
|
||||
if view == FileView::Tree {
|
||||
assert!(!row.contains('✓'));
|
||||
}
|
||||
}
|
||||
assert_eq!(picker.accept(), Some(files[index].path.clone()));
|
||||
assert!(picker.query.is_empty());
|
||||
@@ -5717,22 +5751,35 @@ mod tests {
|
||||
.skip(1)
|
||||
.map(|(index, file)| (index, file.path.clone())),
|
||||
);
|
||||
picker.query = "m.txt".into();
|
||||
picker.refilter();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_file_list(
|
||||
frame,
|
||||
&picker,
|
||||
frame.area(),
|
||||
"",
|
||||
&mut WheelPanels::default(),
|
||||
Some(&files),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
assert!(screen_text(&terminal).contains("M chain/a/m.txt"));
|
||||
assert!(!screen_text(&terminal).contains("R r.txt"));
|
||||
for view in [FileView::Tree, FileView::List] {
|
||||
picker.set_view(view);
|
||||
picker.query = "m.txt".into();
|
||||
picker.refilter();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_file_list(
|
||||
frame,
|
||||
&picker,
|
||||
frame.area(),
|
||||
"",
|
||||
&mut WheelPanels::default(),
|
||||
Some((&files, &BTreeSet::from([1]))),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("✓ M "));
|
||||
assert_eq!(text.matches('✓').count(), 1);
|
||||
assert!(!text.contains("R r.txt"));
|
||||
assert_eq!(
|
||||
picker
|
||||
.entries()
|
||||
.find(|entry| !entry.is_dir)
|
||||
.unwrap()
|
||||
.catalog_index,
|
||||
Some(1)
|
||||
);
|
||||
}
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_file_list(
|
||||
@@ -5746,6 +5793,61 @@ mod tests {
|
||||
})
|
||||
.unwrap();
|
||||
assert!(!screen_text(&terminal).contains("M chain/a/m.txt"));
|
||||
assert!(!screen_text(&terminal).contains('✓'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reviewed_file_prefix_tracks_state_and_clips_in_narrow_panes() {
|
||||
let mut file = deletion_file();
|
||||
file.path = "界.txt".into();
|
||||
file.status = FileStatus::Modified;
|
||||
let files = [file];
|
||||
let mut picker = FilePicker::from_paths(PathBuf::new(), [(0, files[0].path.clone())]);
|
||||
picker.set_view(FileView::List);
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(12, 3)).unwrap();
|
||||
let mut reviewed = BTreeSet::new();
|
||||
for marked in [true, false, true] {
|
||||
if marked {
|
||||
reviewed.insert(0);
|
||||
} else {
|
||||
reviewed.remove(&0);
|
||||
}
|
||||
for width in 1..=8 {
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
frame.render_widget(
|
||||
Paragraph::new("XXXXXXXXXXXX\nXXXXXXXXXXXX\nXXXXXXXXXXXX"),
|
||||
frame.area(),
|
||||
);
|
||||
draw_file_list(
|
||||
frame,
|
||||
&picker,
|
||||
Rect::new(0, 0, width, 3),
|
||||
"",
|
||||
&mut WheelPanels::default(),
|
||||
Some((&files, &reviewed)),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
let prefix = if marked { "> ✓ M " } else { "> M " };
|
||||
for (x, symbol) in prefix.chars().take(usize::from(width)).enumerate() {
|
||||
assert_eq!(buffer[(x as u16, 1)].symbol(), symbol.to_string());
|
||||
}
|
||||
for x in width..12 {
|
||||
assert_eq!(buffer[(x, 1)].symbol(), "X");
|
||||
}
|
||||
if width >= 3 && marked {
|
||||
assert_eq!(buffer[(2, 1)].fg, Color::Green);
|
||||
}
|
||||
if width >= 5 {
|
||||
assert_eq!(buffer[(4, 1)].fg, Color::Yellow);
|
||||
}
|
||||
if width >= 8 {
|
||||
assert_eq!(buffer[(6, 1)].symbol(), "界");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user