feat: toggle diff explorer and show file change indicators
This commit is contained in:
+162
-3
@@ -148,6 +148,7 @@ pub struct DiffState {
|
||||
pub error: Option<String>,
|
||||
pub comparison: Option<Comparison>,
|
||||
pub files: Option<FilePicker>,
|
||||
pub files_visible: bool,
|
||||
pub file: usize,
|
||||
pub focus: Focus,
|
||||
pub top: usize,
|
||||
@@ -177,6 +178,7 @@ impl DiffState {
|
||||
error: None,
|
||||
comparison: None,
|
||||
files: None,
|
||||
files_visible: true,
|
||||
file: 0,
|
||||
focus: Focus::Files,
|
||||
top: 0,
|
||||
@@ -434,6 +436,18 @@ impl DiffState {
|
||||
}
|
||||
|
||||
fn handle_view(&mut self, input: Input) {
|
||||
if input == Input::Ctrl('e') {
|
||||
self.files_visible = !self.files_visible;
|
||||
if self.files_visible {
|
||||
self.focus = Focus::Files;
|
||||
} else if self.focus == Focus::Files {
|
||||
self.focus = Focus::Before;
|
||||
}
|
||||
if let Some(files) = &mut self.files {
|
||||
files.focused = self.files_visible && self.focus == Focus::Files;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.focus == Focus::Files {
|
||||
let files = self.files.as_mut().expect("comparison file picker");
|
||||
match input {
|
||||
@@ -491,14 +505,16 @@ impl DiffState {
|
||||
self.focus = match self.focus {
|
||||
Focus::Files => Focus::Before,
|
||||
Focus::Before => Focus::After,
|
||||
Focus::After => Focus::Files,
|
||||
Focus::After if self.files_visible => Focus::Files,
|
||||
Focus::After => Focus::Before,
|
||||
}
|
||||
}
|
||||
Input::BackTab => {
|
||||
self.focus = match self.focus {
|
||||
Focus::Files => Focus::After,
|
||||
Focus::After => Focus::Before,
|
||||
Focus::Before => Focus::Files,
|
||||
Focus::Before if self.files_visible => Focus::Files,
|
||||
Focus::Before => Focus::After,
|
||||
}
|
||||
}
|
||||
Input::Up | Input::Ctrl('k') => self.navigate(false),
|
||||
@@ -527,7 +543,7 @@ impl DiffState {
|
||||
_ => {}
|
||||
}
|
||||
if let Some(files) = &mut self.files {
|
||||
files.focused = self.focus == Focus::Files;
|
||||
files.focused = self.files_visible && self.focus == Focus::Files;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,6 +1024,149 @@ mod tests {
|
||||
assert!(editor.diff().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explorer_toggle_preserves_filtered_tree_review_and_source_state() {
|
||||
let mut editor = editor();
|
||||
editor
|
||||
.apply(vec![Effect::Replace("unsaved source".into())])
|
||||
.unwrap();
|
||||
let original = editor.context().text;
|
||||
let carets = editor.carets();
|
||||
let revision = editor.text_revision();
|
||||
open(&mut editor);
|
||||
let id = choose_pair(&mut editor);
|
||||
let mut changes = comparison();
|
||||
changes.files[0].path = "src/deep/renamed.txt".into();
|
||||
changes.files[1].path = "src/deep/added.txt".into();
|
||||
editor.apply_diff_result(id, Ok(Outcome::Comparison(changes)));
|
||||
editor.handle(Input::Char('\t'));
|
||||
editor.handle(Input::Down);
|
||||
editor.handle(Input::Right);
|
||||
editor.handle(Input::Char('\t'));
|
||||
editor.handle(Input::Right);
|
||||
editor.handle(Input::Char('\t'));
|
||||
editor.handle(Input::Char('i'));
|
||||
text(&mut editor, "src");
|
||||
editor.handle(Input::Down);
|
||||
let snapshot = |state: &DiffState| {
|
||||
let files = state.files.as_ref().unwrap();
|
||||
(
|
||||
files.query.clone(),
|
||||
files.selected,
|
||||
files.view,
|
||||
files.searching,
|
||||
files
|
||||
.entries()
|
||||
.map(|row| (row.path.to_owned(), row.expanded))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
};
|
||||
let tree = snapshot(editor.diff().unwrap());
|
||||
let rows = editor
|
||||
.diff()
|
||||
.unwrap()
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| (row.before, row.after, row.kind))
|
||||
.collect::<Vec<_>>();
|
||||
editor
|
||||
.bind("tree", "ctrl+e", Some("core.down".into()))
|
||||
.unwrap();
|
||||
editor
|
||||
.bind("selection", "ctrl+e", Some("core.up".into()))
|
||||
.unwrap();
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
let state = editor.diff().unwrap();
|
||||
assert!(!state.files_visible);
|
||||
assert_eq!(state.focus, Focus::Before);
|
||||
assert!(!state.files.as_ref().unwrap().focused);
|
||||
assert_eq!(
|
||||
(state.file, state.top, state.left_before, state.left_after),
|
||||
(0, 1, 1, 1)
|
||||
);
|
||||
assert_eq!(snapshot(state), tree);
|
||||
for (input, focus) in [
|
||||
(Input::Char('\t'), Focus::After),
|
||||
(Input::Char('\t'), Focus::Before),
|
||||
(Input::BackTab, Focus::After),
|
||||
(Input::BackTab, Focus::Before),
|
||||
] {
|
||||
editor.handle(input);
|
||||
let state = editor.diff().unwrap();
|
||||
assert_eq!(state.focus, focus);
|
||||
assert!(!state.files.as_ref().unwrap().focused);
|
||||
}
|
||||
for input in [
|
||||
Input::Char('z'),
|
||||
Input::Ctrl('t'),
|
||||
Input::Backspace,
|
||||
Input::Delete,
|
||||
Input::Paste("not source".into()),
|
||||
Input::Enter,
|
||||
] {
|
||||
editor.handle(input);
|
||||
}
|
||||
assert_eq!(snapshot(editor.diff().unwrap()), tree);
|
||||
editor.handle(Input::Down);
|
||||
assert_eq!(editor.diff().unwrap().top, 2);
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
let state = editor.diff().unwrap();
|
||||
assert!(state.files_visible && state.files.as_ref().unwrap().focused);
|
||||
assert_eq!(state.focus, Focus::Files);
|
||||
assert_eq!(snapshot(state), tree);
|
||||
assert_eq!(
|
||||
(state.file, state.top, state.left_before, state.left_after),
|
||||
(0, 2, 1, 1)
|
||||
);
|
||||
assert_eq!(
|
||||
state
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| (row.before, row.after, row.kind))
|
||||
.collect::<Vec<_>>(),
|
||||
rows
|
||||
);
|
||||
editor.handle(Input::Char('\t'));
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
assert_eq!(editor.diff().unwrap().focus, Focus::Before);
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
editor.handle(Input::BackTab);
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
assert_eq!(editor.diff().unwrap().focus, Focus::After);
|
||||
assert!(!editor.diff().unwrap().files.as_ref().unwrap().focused);
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
assert_eq!(editor.diff().unwrap().focus, Focus::Files);
|
||||
assert_eq!(snapshot(editor.diff().unwrap()), tree);
|
||||
assert_eq!(editor.context().text, original);
|
||||
assert_eq!(editor.carets(), carets);
|
||||
assert_eq!(editor.text_revision(), revision);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.diff().unwrap().files.as_ref().unwrap().searching);
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.diff().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explorer_toggle_is_not_a_revision_selector_control() {
|
||||
let mut editor = editor();
|
||||
open(&mut editor);
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
assert_eq!(editor.diff().unwrap().selected, 0);
|
||||
editor
|
||||
.bind("selection", "ctrl+e", Some("core.down".into()))
|
||||
.unwrap();
|
||||
for phase in [Phase::Before, Phase::After] {
|
||||
assert_eq!(editor.diff().unwrap().phase, phase);
|
||||
editor.handle(Input::Ctrl('e'));
|
||||
let state = editor.diff().unwrap();
|
||||
assert!(state.files_visible);
|
||||
assert_eq!(state.selected, 1);
|
||||
assert_eq!(state.phase, phase);
|
||||
editor.handle(Input::Enter);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aligned_hunks_navigation_shared_top_independent_horizontal_and_file_reset() {
|
||||
let mut editor = editor();
|
||||
|
||||
Reference in New Issue
Block a user