feat: reload clean buffers and guard external-change writes

This commit is contained in:
2026-10-12 22:27:08 +02:00
parent bb5c247ab2
commit 298d586c3e
7 changed files with 556 additions and 41 deletions
+90 -1
View File
@@ -8,6 +8,7 @@ use std::{
mpsc::{self, Receiver, Sender},
},
thread::{self, JoinHandle},
time::{Duration, Instant},
};
use crate::{
@@ -173,6 +174,7 @@ pub struct EditorSession {
diff_workers: BTreeMap<usize, DiffWorker>,
battery: Option<crate::battery::Battery>,
battery_enabled: bool,
last_file_refresh: Instant,
}
impl EditorSession {
@@ -272,6 +274,7 @@ impl EditorSession {
diff_workers: BTreeMap::new(),
battery: None,
battery_enabled: false,
last_file_refresh: Instant::now(),
};
session.synchronize_documents();
session.poll_battery();
@@ -1044,9 +1047,22 @@ impl EditorSession {
changed
}
fn refresh_files_at(&mut self, now: Instant) -> bool {
if now.saturating_duration_since(self.last_file_refresh) < Duration::from_millis(250) {
return false;
}
self.last_file_refresh = now;
self.editor.refresh_files()
}
/// Drains ready plugin work without waiting for command or server responses.
pub fn poll(&mut self) -> bool {
let mut changed = self.synchronize_documents();
self.poll_at(Instant::now())
}
fn poll_at(&mut self, now: Instant) -> bool {
let mut changed = self.refresh_files_at(now);
changed |= self.synchronize_documents();
changed |= self.poll_battery();
changed |= self.poll_diff();
for update in self.lsp.poll() {
@@ -3396,6 +3412,79 @@ end
assert_eq!(session.editor().label("battery"), "");
}
#[test]
fn idle_poll_refreshes_clean_and_parked_files_on_the_interval_but_keeps_dirty_text() {
let fixture = Fixture::new();
let active = fixture.file("buffer.txt", "old");
let parked = fixture.file("parked.txt", "parked");
let mut session = fixture.open("");
session.execute(&format!("edit {}", parked.display()));
session.execute("buffer 1");
let now = session.last_file_refresh;
let revision = session.editor().text_revision();
fs::write(&active, "external replacement").unwrap();
fs::write(&parked, "external parked replacement").unwrap();
session.poll_at(now + Duration::from_millis(249));
assert_eq!(session.editor().lines(), &["old"]);
session.dispatch(Invocation {
plugin: "core".into(),
command: "lsp.definition".into(),
args: String::new(),
});
assert!(session.requests["core"].latest.is_some());
assert!(session.poll_at(now + Duration::from_millis(250)));
assert!(session.requests["core"].latest.is_none());
assert_eq!(session.editor().lines(), &["external replacement"]);
assert!(session.editor().text_revision() > revision);
let documents: Vec<_> = session.editor().documents().collect();
assert_eq!(documents[1].lines, ["external parked replacement"]);
session.handle(Input::Char('x'));
let dirty = session.editor().lines().to_vec();
let revision = session.editor().text_revision();
fs::write(&active, "second external replacement").unwrap();
session.poll_at(now + Duration::from_millis(500));
assert_eq!(session.editor().lines(), dirty.as_slice());
assert_eq!(session.editor().text_revision(), revision);
assert_eq!(
fs::read_to_string(&active).unwrap(),
"second external replacement"
);
}
#[test]
fn clean_external_reload_notifies_async_plugin_with_change_without_save() {
let fixture = Fixture::new();
let file = fixture.file("buffer.txt", "old");
let mut session = fixture.asynchronous(Some(file.clone()));
barrier(&mut session, "opened");
let now = session.last_file_refresh;
let revision = session.editor().text_revision();
fs::write(&file, "updated external text").unwrap();
assert!(session.poll_at(now + Duration::from_millis(250)));
assert_eq!(session.editor().lines(), &["updated external text"]);
let updated_revision = session.editor().text_revision();
assert!(updated_revision > revision);
assert_eq!(
session.documents[&session.editor().buffer_id()]
.snapshot
.revision,
updated_revision
);
barrier(&mut session, "reload delivered");
let events = fixture.events();
let changes: Vec<_> = events
.iter()
.filter(|event| event["event"] == "change")
.collect();
assert_eq!(changes.len(), 1);
assert_eq!(changes[0]["document"]["text"], "updated external text");
assert_eq!(changes[0]["document"]["revision"], updated_revision);
assert!(!events.iter().any(|event| event["event"] == "save"));
}
#[test]
fn async_documents_include_unnamed_hidden_versions_real_saves_renames_and_closes() {
let fixture = Fixture::new();