feat: enable native readonly diff interaction and synchronized pane cursors
This commit is contained in:
+70
-18
@@ -690,21 +690,69 @@ impl DiffState {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn after_display_row(&self, source_row: usize) -> Option<usize> {
|
||||
pub(crate) fn source_display_row(&self, focus: Focus, source_row: usize) -> Option<usize> {
|
||||
self.display_rows.iter().position(|display| {
|
||||
matches!(*display, DisplayRow::Line(index) if self.rows[index].after == Some(source_row))
|
||||
matches!(*display, DisplayRow::Line(index) if self.source_row(focus, index) == Some(source_row))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn after_rows(&self) -> impl DoubleEndedIterator<Item = usize> + '_ {
|
||||
self.excerpt_ranges
|
||||
.iter()
|
||||
.flat_map(|&(start, end)| self.rows[start..end].iter().filter_map(|row| row.after))
|
||||
pub fn after_display_row(&self, source_row: usize) -> Option<usize> {
|
||||
self.source_display_row(Focus::After, source_row)
|
||||
}
|
||||
|
||||
pub(crate) fn clamp_after_row(&self, row: usize) -> Option<usize> {
|
||||
fn source_row(&self, focus: Focus, index: usize) -> Option<usize> {
|
||||
match focus {
|
||||
Focus::Before => self.rows[index].before,
|
||||
Focus::After => self.rows[index].after,
|
||||
Focus::Files => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn source_rows(&self, focus: Focus) -> impl DoubleEndedIterator<Item = usize> + '_ {
|
||||
self.excerpt_ranges.iter().flat_map(move |&(start, end)| {
|
||||
(start..end).filter_map(move |index| self.source_row(focus, index))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn snapshot_lines(&self, focus: Focus) -> Vec<String> {
|
||||
let file = &self.comparison.as_ref().expect("snapshot comparison").files[self.file];
|
||||
let (source, eof, live) = match focus {
|
||||
Focus::Before => (&file.before, file.before_eof, false),
|
||||
Focus::After => (
|
||||
&file.after,
|
||||
file.after_eof,
|
||||
self.live_files.contains(&self.file),
|
||||
),
|
||||
Focus::Files => unreachable!("snapshot belongs to a text pane"),
|
||||
};
|
||||
let mut lines = source.clone();
|
||||
if !live && (eof || lines.is_empty()) {
|
||||
lines.push(String::new());
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
pub(crate) fn corresponding_row(&self, from: Focus, to: Focus, row: usize) -> Option<usize> {
|
||||
let aligned = self.rows.iter().position(|aligned| match from {
|
||||
Focus::Before => aligned.before == Some(row),
|
||||
Focus::After => aligned.after == Some(row),
|
||||
Focus::Files => false,
|
||||
})?;
|
||||
self.display_rows
|
||||
.iter()
|
||||
.filter_map(|display| match *display {
|
||||
DisplayRow::Line(index) => self
|
||||
.source_row(to, index)
|
||||
.map(|row| (index.abs_diff(aligned), row)),
|
||||
DisplayRow::Fold { .. } => None,
|
||||
})
|
||||
.min_by_key(|&(distance, row)| (distance, row))
|
||||
.map(|(_, row)| row)
|
||||
}
|
||||
|
||||
pub(crate) fn clamp_source_row(&self, focus: Focus, row: usize) -> Option<usize> {
|
||||
let mut previous = None;
|
||||
for visible in self.after_rows() {
|
||||
for visible in self.source_rows(focus) {
|
||||
if visible >= row {
|
||||
return Some(match previous {
|
||||
Some(previous) if row - previous < visible - row => previous,
|
||||
@@ -1007,8 +1055,8 @@ impl DiffState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expand_after_excerpt(&mut self, below: bool, source_row: usize) {
|
||||
let Some(display_row) = self.after_display_row(source_row) else {
|
||||
pub(crate) fn expand_source_excerpt(&mut self, focus: Focus, below: bool, source_row: usize) {
|
||||
let Some(display_row) = self.source_display_row(focus, source_row) else {
|
||||
return;
|
||||
};
|
||||
self.expand_excerpt_at(below, self.display_rows[display_row]);
|
||||
@@ -1983,17 +2031,21 @@ mod tests {
|
||||
assert_eq!(editor.diff().unwrap().focus, Focus::Before);
|
||||
editor.handle(Input::Down);
|
||||
editor.handle(Input::Right);
|
||||
assert_eq!(editor.diff().unwrap().top, 1);
|
||||
assert_eq!(editor.diff().unwrap().left_before, 1);
|
||||
assert_eq!(editor.cursor().row, 1);
|
||||
assert_eq!(editor.cursor().column, 1);
|
||||
editor.scroll_diff(Focus::Before, true);
|
||||
editor.set_diff_viewport(id, 0, 1, 1, 0);
|
||||
editor.handle(Input::Char('\t'));
|
||||
assert_eq!(editor.diff().unwrap().focus, Focus::After);
|
||||
editor.handle(Input::Ctrl('j'));
|
||||
editor.handle(Input::Ctrl('l'));
|
||||
assert_eq!(editor.diff().unwrap().top, 2);
|
||||
editor.handle(Input::Down);
|
||||
editor.handle(Input::Right);
|
||||
assert_eq!(editor.cursor().row, 2);
|
||||
assert_eq!(editor.diff().unwrap().top, 1);
|
||||
editor.set_diff_viewport(id, 0, 2, 1, 1);
|
||||
assert_eq!(editor.diff().unwrap().left_after, 1);
|
||||
assert_eq!(editor.diff().unwrap().left_before, 1);
|
||||
editor.handle(Input::End);
|
||||
assert_eq!(editor.diff().unwrap().top, 5);
|
||||
assert_eq!(editor.diff().unwrap().top, 2);
|
||||
editor.handle(Input::BackTab);
|
||||
assert_eq!(editor.diff().unwrap().focus, Focus::Before);
|
||||
editor.handle(Input::BackTab);
|
||||
@@ -2136,12 +2188,12 @@ mod tests {
|
||||
] {
|
||||
editor.handle(input);
|
||||
}
|
||||
assert_eq!(editor.context().text, original);
|
||||
assert_eq!(editor.carets(), carets);
|
||||
editor.handle(Input::Escape);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.diff().is_some());
|
||||
editor.execute("diff.close");
|
||||
assert_eq!(editor.context().text, original);
|
||||
assert_eq!(editor.carets(), carets);
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.context().text, "original\\nsecond");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user