feat: preserve diff context during LSP navigation
This commit is contained in:
+98
-8
@@ -188,6 +188,7 @@ pub struct DiffState {
|
||||
pub error: Option<String>,
|
||||
pub comparison: Option<Comparison>,
|
||||
pub comparison_id: Option<u64>,
|
||||
pub(crate) snapshot_roots: [Option<PathBuf>; 2],
|
||||
pub files: Option<FilePicker>,
|
||||
pub files_visible: bool,
|
||||
pub file: usize,
|
||||
@@ -229,6 +230,7 @@ impl DiffState {
|
||||
comparison: None,
|
||||
comparison_id: None,
|
||||
files: None,
|
||||
snapshot_roots: [None, None],
|
||||
files_visible: true,
|
||||
file: 0,
|
||||
focus: Focus::Files,
|
||||
@@ -264,6 +266,10 @@ impl DiffState {
|
||||
self.pending.take()
|
||||
}
|
||||
|
||||
pub(crate) fn request_queued(&self) -> bool {
|
||||
self.pending.is_some()
|
||||
}
|
||||
|
||||
fn queue(&mut self, operation: Operation) {
|
||||
self.request_kind = Some(match operation {
|
||||
Operation::Compare { .. } => RequestKind::Comparison,
|
||||
@@ -291,7 +297,6 @@ impl DiffState {
|
||||
..
|
||||
}) = &result
|
||||
&& (self.comparison_id != Some(*comparison_id)
|
||||
|| self.file != *file_index
|
||||
|| self.request_kind
|
||||
!= Some(RequestKind::File {
|
||||
comparison_id: *comparison_id,
|
||||
@@ -347,6 +352,7 @@ impl DiffState {
|
||||
self.files = Some(files);
|
||||
self.comparison = Some(comparison);
|
||||
self.comparison_id = Some(id);
|
||||
self.snapshot_roots = [None, None];
|
||||
self.phase = Phase::View;
|
||||
self.file = 0;
|
||||
self.focus = if self.files_visible {
|
||||
@@ -379,8 +385,10 @@ impl DiffState {
|
||||
comparison.editable_paths.remove(&file.path);
|
||||
}
|
||||
*selected = file;
|
||||
self.align_file();
|
||||
self.normalize_focus();
|
||||
if self.file == file_index {
|
||||
self.align_file();
|
||||
self.normalize_focus();
|
||||
}
|
||||
self.error = None;
|
||||
} else {
|
||||
self.error = Some("Unexpected changed-file response".into());
|
||||
@@ -742,9 +750,13 @@ impl DiffState {
|
||||
}
|
||||
|
||||
pub fn after_editable(&self) -> bool {
|
||||
matches!(self.pane_layout(), PaneLayout::Split | PaneLayout::After)
|
||||
&& self.working_after_available()
|
||||
}
|
||||
|
||||
pub(crate) fn working_after_available(&self) -> bool {
|
||||
self.phase == Phase::View
|
||||
&& self.selected_file_loaded()
|
||||
&& matches!(self.pane_layout(), PaneLayout::Split | PaneLayout::After)
|
||||
&& self
|
||||
.after
|
||||
.as_ref()
|
||||
@@ -752,6 +764,7 @@ impl DiffState {
|
||||
&& self.comparison.as_ref().is_some_and(|comparison| {
|
||||
comparison.files.get(self.file).is_some_and(|file| {
|
||||
comparison.editable_paths.contains(&file.path)
|
||||
&& file.status != FileStatus::Removed
|
||||
&& file
|
||||
.notice
|
||||
.as_deref()
|
||||
@@ -770,6 +783,20 @@ impl DiffState {
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
self.select_file_index(index)
|
||||
}
|
||||
|
||||
pub(crate) fn select_file_index(&mut self, index: usize) -> bool {
|
||||
let Some(file) = self
|
||||
.comparison
|
||||
.as_ref()
|
||||
.and_then(|comparison| comparison.files.get(index))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if let Some(files) = &mut self.files {
|
||||
files.reveal_path(&file.path);
|
||||
}
|
||||
self.file = index;
|
||||
self.normalize_focus();
|
||||
self.live_buffer = None;
|
||||
@@ -809,6 +836,69 @@ impl DiffState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_file(&mut self, file_index: usize) {
|
||||
if let Some(comparison_id) = self.comparison_id {
|
||||
self.queue(Operation::LoadFile {
|
||||
comparison_id,
|
||||
file_index,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn source_range_changed(
|
||||
&self,
|
||||
file_index: usize,
|
||||
focus: Focus,
|
||||
first: usize,
|
||||
last: usize,
|
||||
) -> bool {
|
||||
if file_index == self.file {
|
||||
return self.rows.iter().any(|aligned| {
|
||||
aligned.kind != RowKind::Equal
|
||||
&& match focus {
|
||||
Focus::Before => aligned
|
||||
.before
|
||||
.is_some_and(|row| first <= row && row <= last),
|
||||
Focus::After => {
|
||||
aligned.after.is_some_and(|row| first <= row && row <= last)
|
||||
}
|
||||
Focus::Files => false,
|
||||
}
|
||||
});
|
||||
}
|
||||
self.comparison
|
||||
.as_ref()
|
||||
.and_then(|comparison| comparison.files.get(file_index))
|
||||
.is_some_and(|file| {
|
||||
file.hunks.iter().any(|hunk| {
|
||||
let (start, len) = match focus {
|
||||
Focus::Before => (hunk.before_start, hunk.before_len),
|
||||
Focus::After => (hunk.after_start, hunk.after_len),
|
||||
Focus::Files => return false,
|
||||
};
|
||||
len > 0 && start <= last && first < start.saturating_add(len)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn reveal_source_row(&mut self, focus: Focus, row: usize) {
|
||||
if self.source_display_row(focus, row).is_none()
|
||||
&& let Some(index) = self.rows.iter().position(|aligned| match focus {
|
||||
Focus::Before => aligned.before == Some(row),
|
||||
Focus::After => aligned.after == Some(row),
|
||||
Focus::Files => false,
|
||||
})
|
||||
{
|
||||
self.excerpt_ranges.push((index, index + 1));
|
||||
self.excerpt_ranges.sort_unstable();
|
||||
self.rebuild_display_rows();
|
||||
}
|
||||
if let Some(display) = self.source_display_row(focus, row) {
|
||||
self.top = display;
|
||||
self.follow_caret = true;
|
||||
}
|
||||
}
|
||||
|
||||
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.source_row(focus, index) == Some(source_row))
|
||||
@@ -833,14 +923,14 @@ impl DiffState {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn snapshot_lines(&self, focus: Focus) -> Vec<String> {
|
||||
let file = &self.comparison.as_ref().expect("snapshot comparison").files[self.file];
|
||||
pub(crate) fn snapshot_lines_at(&self, file_index: usize, focus: Focus) -> Vec<String> {
|
||||
let file = &self.comparison.as_ref().expect("snapshot comparison").files[file_index];
|
||||
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),
|
||||
self.live_files.contains(&file_index),
|
||||
),
|
||||
Focus::Files => unreachable!("snapshot belongs to a text pane"),
|
||||
};
|
||||
@@ -901,7 +991,7 @@ impl DiffState {
|
||||
}
|
||||
|
||||
pub fn refresh_after(&mut self, lines: &[String], revision: u64) {
|
||||
if !self.after_editable() {
|
||||
if !self.working_after_available() {
|
||||
return;
|
||||
}
|
||||
if self.live_revision == Some(revision) {
|
||||
|
||||
Reference in New Issue
Block a user