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:
1 parent
906d64cf52
commit
f5faca7575
5 files changed
+153
-13
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ©, 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user