C14.15 r2: failures shown once per kind of error; a recovered tab keeps its point's time

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-12 11:39:04 -04:00
1 parent 906d64cf52
commit f5faca7575
5 files changed
+153 -13

No files matched your search

+1 -1
View File
@@ -53,7 +53,7 @@ pub struct Extra {
pub pointing: bool,
/// When a point last landed, and the failure last shown.
pub pointed_at: Option<std::time::SystemTime>,
pub point_failed: Option<String>,
pub point_failed: Option<std::io::ErrorKind>,
/// A kept tab's mode, place and text came back once.
pub restored: bool,
/// Disk reads started; only the newest lands.
+14 -11
View File
@@ -46,8 +46,8 @@ pub struct Keeping {
crash: Option<Crash>,
waiting: Vec<Waiting>,
written: Option<String>,
/// The session write failure last shown.
unwritten: Option<String>,
/// The kind of session write failure last shown.
unwritten: Option<io::ErrorKind>,
/// A quit began: the kept list stays as then.
pub leaving: bool,
/// The list as kept when the quit began.
@@ -227,6 +227,8 @@ impl Desk {
let tab = self.tab_mut(id).expect("open");
// A recovered copy keeps writing to the same points.
tab.extra.slot = kept.slot.clone();
// Its point stands until a newer lands.
tab.extra.pointed_at = back.as_ref().map(|(_, at)| *at);
let view = tab.view.clone();
if let Some((_, at)) = &back {
let at = humantime(*at);
@@ -334,14 +336,15 @@ impl Desk {
tab.extra.point_failed = None;
return;
}
Err(error) => error.to_string(),
Err(error) => error,
};
log::error!("mark: a recovery point for tab {id} failed: {error}");
// Each failure shows once; the next tick retries.
if tab.extra.point_failed.as_ref() == Some(&error) {
// Each kind of failure shows once; the next tick retries.
if tab.extra.point_failed == Some(error.kind()) {
return;
}
tab.extra.point_failed = Some(error.clone());
tab.extra.point_failed = Some(error.kind());
let error = error.to_string();
let at = match tab.extra.pointed_at {
Some(at) => humantime(at),
None => text(cx, "office.stopped.never", &[]).to_string(),
@@ -363,13 +366,13 @@ impl Desk {
self.keeping.unwritten = None;
return;
}
Err(error) => error.to_string(),
Err(error) => error,
};
log::error!("mark: the session did not save: {error}");
// Each failure shows once; the next tick retries.
if self.keeping.unwritten.as_ref() != Some(&error) {
self.keeping.unwritten = Some(error.clone());
let said = text(cx, "mark.session.failed", &[("error", &error)]);
// Each kind of failure shows once; the next tick retries.
if self.keeping.unwritten != Some(error.kind()) {
self.keeping.unwritten = Some(error.kind());
let said = text(cx, "mark.session.failed", &[("error", &error.to_string())]);
self.toast(said, ely_gpui_component::primitives::Severity::Danger, cx);
}
}
+132
View File
@@ -309,4 +309,136 @@ mod window {
"once"
);
}
/// Types `typed` into the shown tab, then keeps.
fn type_and_keep(
desk: &Entity<Desk>,
view: &Entity<MarkView>,
typed: &str,
cx: &mut VisualTestContext,
) {
view.update(cx, |view, cx| view.adopt(typed.into(), cx));
cx.executor()
.advance_clock(std::time::Duration::from_secs(1));
cx.run_until_parked();
desk.update(cx, |desk, cx| desk.keep(cx));
cx.run_until_parked();
}
#[gpui::test]
fn a_recovered_tab_names_its_point_when_the_next_fails(
first: &mut TestAppContext,
second: &mut TestAppContext,
) {
let (data, files) = (
tempfile::tempdir().expect("data"),
tempfile::tempdir().expect("files"),
);
let a = files.path().join("a.md");
std::fs::write(
&a, "Text.
",
)
.expect("a");
let (desk, cx) = start(first, data.path());
open(&desk, &a, cx);
let view = show(&desk, "a.md", cx);
type_and_keep(
&desk,
&view,
"Changed.
",
cx,
);
std::fs::write(data.path().join("session.lock"), "open 4294967295").expect("a lock");
let (desk, cx) = start(second, data.path());
let copy = desk.read_with(cx, |desk, _| {
let tab = desk
.core
.tabs
.iter()
.find(|tab| tab.session.path().is_none());
tab.expect("an untitled copy").name.to_string()
});
let view = show(&desk, &copy, cx);
let recovery = data.path().join("recovery");
std::fs::remove_dir_all(&recovery).expect("removes");
std::fs::write(&recovery, "not a folder").expect("blocks");
type_and_keep(
&desk, &view, "Again.
", cx,
);
assert_eq!(toasted(&desk, "was not kept for recovery", cx), 1);
assert_eq!(
toasted(&desk, "none yet", cx),
0,
"the recovered point's time shows"
);
}
/// Every folder under `dir` read-only, or writable again.
#[cfg(unix)]
fn lock_down(dir: &std::path::Path, locked: bool) {
use std::os::unix::fs::PermissionsExt as _;
let mode = if locked { 0o500 } else { 0o700 };
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(mode)).expect("permissions");
for entry in std::fs::read_dir(dir).expect("lists") {
let path = entry.expect("an entry").path();
if path.is_dir() {
lock_down(&path, locked);
}
}
}
#[cfg(unix)]
#[gpui::test]
fn denied_writes_show_once_each(cx: &mut TestAppContext) {
let (data, files) = (
tempfile::tempdir().expect("data"),
tempfile::tempdir().expect("files"),
);
let a = files.path().join("a.md");
std::fs::write(
&a, "Text.
",
)
.expect("a");
let (desk, cx) = start(cx, data.path());
open(&desk, &a, cx);
let view = show(&desk, "a.md", cx);
type_and_keep(
&desk, &view, "One.
", cx,
);
lock_down(&data.path().join("recovery"), true);
type_and_keep(
&desk, &view, "Two.
", cx,
);
type_and_keep(
&desk, &view, "Three.
", cx,
);
lock_down(&data.path().join("recovery"), false);
assert_eq!(
toasted(&desk, "was not kept for recovery", cx),
1,
"a point"
);
lock_down(data.path(), true);
for line in [1, 2] {
view.update(cx, |view, cx| {
view.document()
.update(cx, |document, cx| document.set_caret(line, 0, cx))
});
desk.update(cx, |desk, cx| desk.keep(cx));
cx.run_until_parked();
}
lock_down(data.path(), false);
assert_eq!(
toasted(&desk, "were not saved for the next launch", cx),
1,
"the session"
);
}
}