fix: retain repository status between picker openings

This commit is contained in:
Valdemar Lorenzen
2026-10-14 14:36:42 +02:00
parent 77a9111f45
commit 1cb9a0ddfc
7 changed files with 166 additions and 73 deletions
+87 -28
View File
@@ -376,7 +376,7 @@ pub struct EditorSession {
gutter_request_id: u64,
gutter_pending: bool,
last_gutter_refresh: Option<Instant>,
picker_source: Option<(usize, PathBuf)>,
picker_source: Option<PathBuf>,
battery: Option<crate::battery::Battery>,
battery_enabled: bool,
last_file_refresh: Instant,
@@ -1275,15 +1275,8 @@ impl EditorSession {
fn poll_gutter_at(&mut self, now: Instant) -> bool {
let line_gutter = self.editor.gutter_style() != crate::gutter::GutterStyle::None;
let picker_changed = match (self.editor.normal_file_picker(), &self.picker_source) {
(Some(picker), Some((tab, root))) => {
*tab != self.editor.tab_id() || *root != picker.root
}
(None, None) => false,
_ => true,
};
if self.editor.should_quit() || (!line_gutter && self.editor.normal_file_picker().is_none())
{
let picker_changed = self.picker_source.as_deref() != Some(self.editor.project_root());
if self.editor.should_quit() {
self.gutter_worker = None;
self.gutter_sources.clear();
self.gutter_pending = false;
@@ -1320,10 +1313,7 @@ impl EditorSession {
.is_none_or(|last| now.saturating_duration_since(last) >= Duration::from_secs(1));
if sources_changed || picker_changed || refresh_due {
if picker_changed {
self.picker_source = self
.editor
.normal_file_picker()
.map(|picker| (self.editor.tab_id(), picker.root.clone()));
self.picker_source = Some(self.editor.project_root().to_owned());
}
if sources_changed {
self.gutter_sources = self
@@ -1357,11 +1347,6 @@ impl EditorSession {
}
self.gutter_request_id = self.gutter_request_id.wrapping_add(1);
self.last_gutter_refresh = Some(now);
if self.gutter_sources.is_empty() && self.picker_source.is_none() {
self.gutter_worker = None;
self.gutter_pending = false;
return false;
}
if self.gutter_worker.is_none() {
match crate::gutter::Worker::start() {
Ok(worker) => self.gutter_worker = Some(worker),
@@ -1383,7 +1368,7 @@ impl EditorSession {
)
})
.collect();
let picker = self.picker_source.as_ref().map(|(_, root)| {
let picker = self.picker_source.as_ref().map(|root| {
let buffers = self
.gutter_sources
.iter()
@@ -1451,20 +1436,15 @@ impl EditorSession {
}
fn apply_picker_result(&mut self, id: u64, reply: crate::gutter::PickerChanges) -> bool {
let Some((tab, root)) = &self.picker_source else {
let Some(root) = &self.picker_source else {
return false;
};
if id != self.gutter_request_id
|| *tab != self.editor.tab_id()
|| *root != reply.root
|| self
.editor
.normal_file_picker()
.is_none_or(|picker| picker.root != *root)
|| *root != self.editor.project_root()
{
return false;
}
let tab = *tab;
let mut changed = false;
let changes = match reply.result {
Ok(changes) => changes,
@@ -1477,7 +1457,7 @@ impl EditorSession {
changed
| self
.editor
.set_picker_repository_changes(tab, &reply.root, changes)
.set_picker_repository_changes(&reply.root, changes)
}
pub(crate) fn refresh_gutter(&mut self) -> bool {
@@ -1816,6 +1796,16 @@ mod tests {
session.handle(Input::Char(' '));
session.handle(Input::Char('o'));
};
let status = |session: &EditorSession| {
session
.editor
.normal_file_picker()
.unwrap()
.entries()
.find(|row| row.path == Path::new("buffer.txt"))
.unwrap()
.status
};
let ready = |session: &mut EditorSession, clock: Instant, expected| {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
@@ -1845,16 +1835,56 @@ mod tests {
thread::sleep(Duration::from_millis(5));
}
};
let settle = |session: &mut EditorSession| {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
session.poll_gutter_at(now);
if !session.gutter_pending {
break;
}
assert!(
Instant::now() < deadline,
"background file status did not settle"
);
thread::sleep(Duration::from_millis(5));
}
};
settle(&mut session);
open(&mut session);
assert_eq!(
status(&session),
Some(FileStatus::Modified),
"first open must reuse status loaded while the picker was closed",
);
ready(&mut session, now, Some(FileStatus::Modified));
session.handle(Input::Ctrl('w'));
settle(&mut session);
open(&mut session);
assert_eq!(
status(&session),
Some(FileStatus::Modified),
"reopening must retain completed status",
);
let request = session.gutter_request_id;
session.poll_gutter_at(now);
assert_eq!(
session.gutter_request_id, request,
"opening must not restart status work"
);
session.handle(Input::Ctrl('w'));
session.poll_gutter_at(now);
session
.editor
.apply(vec![Effect::Replace("base\n".into())])
.unwrap();
assert!(session.editor.dirty());
settle(&mut session);
open(&mut session);
assert_eq!(
status(&session),
None,
"closed-picker edits must refresh the cache"
);
ready(&mut session, now, None);
assert_eq!(
fs::read_to_string(fixture.0.join("buffer.txt")).unwrap(),
@@ -1863,11 +1893,40 @@ mod tests {
session.handle(Input::Ctrl('w'));
session.poll_gutter_at(now);
session.handle(Input::Char('u'));
settle(&mut session);
open(&mut session);
assert_eq!(status(&session), Some(FileStatus::Modified));
ready(&mut session, now, Some(FileStatus::Modified));
git(&["add", "buffer.txt"]);
git(&["commit", "-qm", "new baseline"]);
ready(&mut session, now + Duration::from_secs(1), None);
assert!(!session.apply_picker_result(
session.gutter_request_id.wrapping_sub(1),
crate::gutter::PickerChanges {
root: fixture.0.clone(),
result: Ok(Vec::new()),
},
));
assert!(!session.apply_picker_result(
session.gutter_request_id,
crate::gutter::PickerChanges {
root: fixture.0.join("elsewhere"),
result: Ok(Vec::new()),
},
));
session.handle(Input::Ctrl('w'));
session.execute("tab.new");
open(&mut session);
assert_eq!(status(&session), None);
assert!(
session
.editor
.normal_file_picker()
.unwrap()
.entries()
.any(|row| row.path == Path::new("added.txt")
&& row.status == Some(FileStatus::Added))
);
}
#[test]