Fixes for M-02 to M-14, G-01 to G-14, K-02 to K-04, X-04 to X-14, not yet reviewed
Committed before review so Windows and WSL builds can start. Each fix was tested and mutation-checked here; the codex review of each item is still to come, so tasks/BUGS.md stays unticked. M-02 is mid-review: round 1 findings are fixed (apps/ely-mark/src/tally.rs). K-01 stays deferred. The ElyMark window test mark-outside.sh failed once at its crash-recovery step in the last run; not yet looked into.
This commit is contained in:
75 files changed
+1482
-354
No files matched your search
@@ -120,6 +120,7 @@ impl Desk {
|
||||
return self.set_outside(id, Outside::Unread(why), cx);
|
||||
}
|
||||
tab.extra.base = disk.text.clone();
|
||||
tab.mark = crate::engine::mark(&disk.text);
|
||||
tab.extra.restart_autosave();
|
||||
tab.doc.lock().expect("the text lock").source = disk;
|
||||
let view = tab.view.clone();
|
||||
|
||||
@@ -53,7 +53,9 @@ pub struct Desk {
|
||||
pub keeping: crate::restore::Keeping,
|
||||
/// The export dialog's choice, while it is open.
|
||||
pub output: Option<crate::export::Output>,
|
||||
pub printing: Option<crate::export::Printing>,
|
||||
/// Pages printing to PDF, by job; several may run.
|
||||
pub printing: HashMap<u64, crate::export::Printing>,
|
||||
pub print_jobs: u64,
|
||||
pub look: crate::look::Look,
|
||||
pub look_file: PathBuf,
|
||||
/// Git's word on the shown file, and which tab asked.
|
||||
@@ -113,7 +115,7 @@ impl Host for Desk {
|
||||
self.watch_folders(cx);
|
||||
let look = serde_json::to_value(&self.look).expect("a look serializes");
|
||||
view.update(cx, |view, cx| view.set_look(look, cx));
|
||||
let changed = view.read(cx).changed;
|
||||
let changed = view.read(cx).changed();
|
||||
self.autosave(id, changed, cx);
|
||||
let recheck = self.tab_mut(id).is_some_and(|tab| {
|
||||
if !tab.dirty(cx) {
|
||||
@@ -200,7 +202,8 @@ pub fn open(app: AppId, window: &mut Window, cx: &mut App) -> Entity<Root> {
|
||||
comparing: None,
|
||||
keeping,
|
||||
output: None,
|
||||
printing: None,
|
||||
printing: Default::default(),
|
||||
print_jobs: 0,
|
||||
look: look.clone(),
|
||||
look_file,
|
||||
tracked: None,
|
||||
@@ -228,8 +231,13 @@ pub fn open(app: AppId, window: &mut Window, cx: &mut App) -> Entity<Root> {
|
||||
desk.open_path(path, cx);
|
||||
}
|
||||
}
|
||||
for name in desk.keeping.untitled() {
|
||||
for (at, name) in desk.keeping.untitled() {
|
||||
// Tab numbers come from `next` as the load starts.
|
||||
let id = desk.core.next;
|
||||
desk.new_from_text(&name, "", cx);
|
||||
if desk.core.next > id {
|
||||
desk.keeping.made(at, id);
|
||||
}
|
||||
}
|
||||
for path in asked {
|
||||
desk.open_path(path, cx);
|
||||
@@ -246,8 +254,13 @@ pub fn open(app: AppId, window: &mut Window, cx: &mut App) -> Entity<Root> {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
cx.on_app_quit(|desk, _| {
|
||||
desk.keeping.close();
|
||||
cx.on_app_quit(|desk, cx| {
|
||||
// A system end skips asking: unsaved tabs keep points.
|
||||
if desk.core.tabs.iter().any(|tab| tab.dirty(cx)) {
|
||||
log::warn!("mark: quit with unsaved tabs; points stay");
|
||||
} else {
|
||||
desk.keeping.close();
|
||||
}
|
||||
async {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -132,7 +132,7 @@ impl Engine for Mark {
|
||||
}
|
||||
|
||||
fn edited(view: &MarkView) -> u64 {
|
||||
view.changed
|
||||
view.changed()
|
||||
}
|
||||
|
||||
fn edit_pending(view: &MarkView) -> bool {
|
||||
|
||||
+40
-23
@@ -108,8 +108,7 @@ impl Desk {
|
||||
let Some(id) = self.active_tab().map(|tab| tab.id) else {
|
||||
return;
|
||||
};
|
||||
let pdf = self.core.work.join(format!("print-{id}.pdf"));
|
||||
self.render_pdf(id, pdf, None, cx);
|
||||
self.render_pdf(id, None, cx);
|
||||
}
|
||||
|
||||
/// Asks where, renders the whole text, then writes it.
|
||||
@@ -126,20 +125,28 @@ impl Desk {
|
||||
let dir = ely_office::folder(tab).unwrap_or_else(std::env::temp_dir);
|
||||
let chosen = cx.prompt_for_new_path(&dir, Some(&format!("{stem}.{}", output.extension())));
|
||||
cx.spawn(async move |desk, cx| {
|
||||
let Ok(Ok(Some(path))) = chosen.await else {
|
||||
return log::info!("mark: the export panel closed without a path");
|
||||
// A cancel says nothing; a panel that failed says why.
|
||||
let failed = match chosen.await {
|
||||
Ok(Ok(Some(path))) => Ok(path),
|
||||
Ok(Ok(None)) => return log::info!("mark: the export panel was cancelled"),
|
||||
Ok(Err(error)) => Err(error.to_string()),
|
||||
Err(error) => Err(error.to_string()),
|
||||
};
|
||||
let path = match failed {
|
||||
Ok(path) => path,
|
||||
Err(why) => {
|
||||
if desk.update(cx, |desk, cx| desk.panel_failed(&why, cx)).is_err() {
|
||||
log::error!("mark: the export panel failed after its window: {why}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
desk.update(cx, |desk, cx| {
|
||||
if let Err(error) = not_open(desk, &path) {
|
||||
return desk.exported(Err(error), cx);
|
||||
}
|
||||
match output {
|
||||
Output::Pdf => desk.render_pdf(
|
||||
id,
|
||||
desk.core.work.join(format!("export-{id}.pdf")),
|
||||
Some(path),
|
||||
cx,
|
||||
),
|
||||
Output::Pdf => desk.render_pdf(id, Some(path), cx),
|
||||
_ => desk.write_output(id, output, path, cx),
|
||||
}
|
||||
})
|
||||
@@ -257,7 +264,11 @@ impl Desk {
|
||||
}
|
||||
|
||||
/// The whole text as PDF; then to `to`, else printed.
|
||||
fn render_pdf(&mut self, id: u64, pdf: PathBuf, to: Option<PathBuf>, cx: &mut Context<Self>) {
|
||||
fn render_pdf(&mut self, id: u64, to: Option<PathBuf>, cx: &mut Context<Self>) {
|
||||
// Each job has its own files; none cancels another.
|
||||
self.print_jobs += 1;
|
||||
let n = self.print_jobs;
|
||||
let pdf = self.core.work.join(format!("print-{n}.pdf"));
|
||||
let Some(tab) = self.tab(id) else {
|
||||
return;
|
||||
};
|
||||
@@ -273,14 +284,20 @@ impl Desk {
|
||||
view.render(
|
||||
"html",
|
||||
move |rendered, cx: &mut App| {
|
||||
let staged = work.join(format!("print-{id}.html"));
|
||||
let page = rendered.map_err(io::Error::other).and_then(|rendered| {
|
||||
let (html, images) = paper::Marked::read(&rendered)?.inline(&folder);
|
||||
let style = format!("<style>{}</style>", std::fs::read_to_string(&style)?);
|
||||
std::fs::write(&staged, paper::page(&name, &style, &html))?;
|
||||
Ok(Lost::of(images, &rendered))
|
||||
});
|
||||
let staged = work.join(format!("print-{n}.html"));
|
||||
let writing = staged.clone();
|
||||
cx.spawn(async move |cx| {
|
||||
// Images inline and the page writes off the window thread.
|
||||
let page = cx
|
||||
.background_spawn(async move {
|
||||
rendered.map_err(io::Error::other).and_then(|rendered| {
|
||||
let (html, images) = paper::Marked::read(&rendered)?.inline(&folder);
|
||||
let style = format!("<style>{}</style>", std::fs::read_to_string(&style)?);
|
||||
std::fs::write(&writing, paper::page(&name, &style, &html))?;
|
||||
Ok(Lost::of(images, &rendered))
|
||||
})
|
||||
})
|
||||
.await;
|
||||
in_window(&desk, cx, |desk, window, cx| match page {
|
||||
Ok(lost) => {
|
||||
let job = Job {
|
||||
@@ -289,7 +306,7 @@ impl Desk {
|
||||
open,
|
||||
lost,
|
||||
};
|
||||
desk.print_page(&staged, job, window, cx)
|
||||
desk.print_page(n, &staged, job, window, cx)
|
||||
}
|
||||
Err(error) => desk.exported(Err(error), cx),
|
||||
})
|
||||
@@ -303,7 +320,7 @@ impl Desk {
|
||||
}
|
||||
|
||||
/// Chromium prints the staged page; the PDF goes where asked.
|
||||
fn print_page(&mut self, page: &Path, job: Job, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn print_page(&mut self, n: u64, page: &Path, job: Job, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let root = page
|
||||
.parent()
|
||||
.expect("a staged page sits in a folder")
|
||||
@@ -315,20 +332,20 @@ impl Desk {
|
||||
let heard = cx.subscribe(&view, move |desk, view, event: &Event, cx| match event {
|
||||
Event::Loaded => view.read(cx).print_pdf(&job.pdf),
|
||||
Event::Printed(path, ok) => {
|
||||
desk.printing = None;
|
||||
desk.printing.remove(&n);
|
||||
let printed = ok
|
||||
.then(|| path.clone())
|
||||
.ok_or_else(|| io::Error::other("Chromium did not print the page"));
|
||||
desk.printed(printed, job.clone(), cx);
|
||||
}
|
||||
Event::Failed(why) => {
|
||||
desk.printing = None;
|
||||
desk.printing.remove(&n);
|
||||
desk.exported(Err(io::Error::other(why.clone())), cx);
|
||||
}
|
||||
Event::Query(_, reply) => reply.ok(""),
|
||||
Event::Leave(_) => {}
|
||||
});
|
||||
self.printing = Some(Printing {
|
||||
self.printing.insert(n, Printing {
|
||||
_view: view,
|
||||
_heard: heard,
|
||||
});
|
||||
|
||||
@@ -115,8 +115,12 @@ impl Desk {
|
||||
|
||||
fn close_find(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.finder = None;
|
||||
if let Some(view) = self.active_view() {
|
||||
// Every tab's page holds its own highlights.
|
||||
let views: Vec<_> = self.core.tabs.iter().map(|tab| tab.view.clone()).collect();
|
||||
for view in views {
|
||||
view.update(cx, |view, cx| view.unfind(cx));
|
||||
}
|
||||
if let Some(view) = self.active_view() {
|
||||
let focus = view.read(cx).focus.clone();
|
||||
window.focus(&focus, cx);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ mod places;
|
||||
mod rename;
|
||||
mod restore;
|
||||
mod side;
|
||||
mod tally;
|
||||
mod text;
|
||||
mod vcs;
|
||||
mod view;
|
||||
|
||||
@@ -308,6 +308,8 @@ impl Desk {
|
||||
return None;
|
||||
}
|
||||
tab.extra.base = disk.text.clone();
|
||||
// The file holds the disk's text: it is saved.
|
||||
tab.mark = crate::engine::mark(&disk.text);
|
||||
tab.extra.restart_autosave();
|
||||
// Its bytes (BOM, line ends) are what saves keep now.
|
||||
tab.doc.lock().expect("the text lock").source = disk.clone();
|
||||
|
||||
@@ -75,6 +75,11 @@ pub fn open(
|
||||
));
|
||||
}
|
||||
|
||||
/// The disk's text in place; reports start a new generation.
|
||||
pub fn refresh(view: &WebView, markdown: &str, load: u64) {
|
||||
view.run_js(&format!("ely.refresh({}, {load})", json!(markdown)));
|
||||
}
|
||||
|
||||
/// The same text in another mode; pending edits go first.
|
||||
pub fn switch(view: &WebView, mode: Mode) {
|
||||
view.run_js(&format!("ely.mode('{}')", mode.name()));
|
||||
|
||||
@@ -37,6 +37,8 @@ struct Waiting {
|
||||
kept: Kept,
|
||||
real: Option<PathBuf>,
|
||||
text: Option<(String, SystemTime)>,
|
||||
/// The untitled tab made for it, once made.
|
||||
tab: Option<u64>,
|
||||
}
|
||||
|
||||
/// The session's files and what still waits to restore.
|
||||
@@ -97,6 +99,7 @@ impl Keeping {
|
||||
},
|
||||
real: Some(real),
|
||||
text: None,
|
||||
tab: None,
|
||||
}),
|
||||
Err(error) => {
|
||||
log::warn!("mark: a kept file does not open: {error}");
|
||||
@@ -111,6 +114,7 @@ impl Keeping {
|
||||
kept,
|
||||
real: None,
|
||||
text,
|
||||
tab: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -132,13 +136,16 @@ impl Keeping {
|
||||
Ok((keeping, paths, missing))
|
||||
}
|
||||
|
||||
/// The untitled tabs to make, with their names.
|
||||
pub fn untitled(&self) -> Vec<String> {
|
||||
let untitled = self
|
||||
.waiting
|
||||
.iter()
|
||||
.filter(|waiting| waiting.kept.path.is_none());
|
||||
untitled.map(|waiting| waiting.kept.name.clone()).collect()
|
||||
/// The untitled tabs to make: their place here and name.
|
||||
pub fn untitled(&self) -> Vec<(usize, String)> {
|
||||
let waiting = self.waiting.iter().enumerate();
|
||||
let untitled = waiting.filter(|(_, waiting)| waiting.kept.path.is_none() && waiting.tab.is_none());
|
||||
untitled.map(|(at, waiting)| (at, waiting.kept.name.clone())).collect()
|
||||
}
|
||||
|
||||
/// Entry `at` belongs to untitled tab `tab`.
|
||||
pub fn made(&mut self, at: usize, tab: u64) {
|
||||
self.waiting[at].tab = Some(tab);
|
||||
}
|
||||
|
||||
/// A clean end: no crash next time, no stale points.
|
||||
@@ -177,6 +184,15 @@ fn clear(dir: &Path) -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// What tab `id` restores: by file, or untitled by number.
|
||||
fn waiting_for(waiting: &[Waiting], real: Option<&PathBuf>, id: u64) -> Option<usize> {
|
||||
waiting.iter().position(|waiting| match (&waiting.real, real) {
|
||||
(Some(kept), Some(open)) => kept == open,
|
||||
(None, None) => waiting.tab == Some(id),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
impl Desk {
|
||||
/// A tab just shown takes its kept mode, place, text.
|
||||
pub fn restore_shown(&mut self, id: u64, cx: &mut Context<Self>) {
|
||||
@@ -190,16 +206,8 @@ impl Desk {
|
||||
.session
|
||||
.path()
|
||||
.and_then(|path| std::fs::canonicalize(path).ok());
|
||||
let untitled = tab.session.path().is_none();
|
||||
let waiting = &mut self.keeping.waiting;
|
||||
let Some(at) = waiting
|
||||
.iter()
|
||||
.position(|waiting| match (&waiting.real, &real) {
|
||||
(Some(kept), Some(open)) => kept == open,
|
||||
(None, None) => untitled,
|
||||
_ => false,
|
||||
})
|
||||
else {
|
||||
let Some(at) = waiting_for(waiting, real.as_ref(), id) else {
|
||||
return;
|
||||
};
|
||||
let Waiting {
|
||||
@@ -392,4 +400,19 @@ mod tests {
|
||||
let place = kept.place.expect("a place");
|
||||
assert_eq!((place.line, place.column), (3, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_untitled_copy_goes_to_the_tab_made_for_it() {
|
||||
let untitled = |name: &str, tab: u64| super::Waiting {
|
||||
kept: super::Kept { path: None, name: name.into(), mode: "visual".into(), place: None, slot: String::new() },
|
||||
real: None,
|
||||
text: None,
|
||||
tab: Some(tab),
|
||||
};
|
||||
let waiting = [untitled("Untitled1", 4), untitled("Untitled2", 5)];
|
||||
// The second tab shows first: it still takes its own.
|
||||
assert_eq!(super::waiting_for(&waiting, None, 5), Some(1));
|
||||
assert_eq!(super::waiting_for(&waiting, None, 4), Some(0));
|
||||
assert_eq!(super::waiting_for(&waiting, None, 9), None, "a new tab takes nothing");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//! Which page reports count, by page load and by version.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// One page load's change counts and the renders it owes.
|
||||
pub struct Tally<R> {
|
||||
load: u64,
|
||||
/// The newest change the page announced.
|
||||
changed: u64,
|
||||
/// The change whose text arrived.
|
||||
reported: u64,
|
||||
renders: HashMap<u64, R>,
|
||||
asked: u64,
|
||||
}
|
||||
|
||||
impl<R> Tally<R> {
|
||||
pub fn new() -> Self {
|
||||
Tally { load: 0, changed: 0, reported: 0, renders: HashMap::new(), asked: 0 }
|
||||
}
|
||||
|
||||
pub fn load(&self) -> u64 {
|
||||
self.load
|
||||
}
|
||||
|
||||
pub fn changed(&self) -> u64 {
|
||||
self.changed
|
||||
}
|
||||
|
||||
/// Every change's text has arrived.
|
||||
pub fn settled(&self) -> bool {
|
||||
self.reported == self.changed
|
||||
}
|
||||
|
||||
/// A reload or refresh: older reports lapse, owed renders end.
|
||||
pub fn restart(&mut self) -> Vec<R> {
|
||||
self.load += 1;
|
||||
(self.changed, self.reported) = (0, 0);
|
||||
self.lapse()
|
||||
}
|
||||
|
||||
/// The renders owed, as the page fails or restarts.
|
||||
pub fn lapse(&mut self) -> Vec<R> {
|
||||
self.renders.drain().map(|(_, render)| render).collect()
|
||||
}
|
||||
|
||||
/// Whether a report from page load `generation` still counts.
|
||||
pub fn current(&self, generation: Option<u64>) -> bool {
|
||||
generation.is_none_or(|load| load == self.load)
|
||||
}
|
||||
|
||||
pub fn change(&mut self, version: u64) {
|
||||
self.changed = self.changed.max(version);
|
||||
}
|
||||
|
||||
/// Takes text `version` if it is the newest.
|
||||
pub fn text(&mut self, version: u64) -> bool {
|
||||
let newest = version >= self.reported;
|
||||
if newest {
|
||||
self.reported = version;
|
||||
}
|
||||
newest
|
||||
}
|
||||
|
||||
/// Owes `render`; its number goes to the page.
|
||||
pub fn ask(&mut self, render: R) -> u64 {
|
||||
self.asked += 1;
|
||||
self.renders.insert(self.asked, render);
|
||||
self.asked
|
||||
}
|
||||
|
||||
pub fn answered(&mut self, ask: u64) -> Option<R> {
|
||||
self.renders.remove(&ask)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Tally;
|
||||
|
||||
#[test]
|
||||
fn a_key_from_before_a_refresh_leaves_it_settled() {
|
||||
let mut tally = Tally::<()>::new();
|
||||
let old = tally.load();
|
||||
tally.restart();
|
||||
// The renderer's change for the old text lands late.
|
||||
if tally.current(Some(old)) {
|
||||
tally.change(1);
|
||||
}
|
||||
assert!(tally.settled(), "an old change does not count");
|
||||
// The refreshed page's own edits count again.
|
||||
tally.change(1);
|
||||
assert!(!tally.settled());
|
||||
assert!(tally.text(1));
|
||||
assert!(tally.settled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_restart_ends_every_owed_render() {
|
||||
let mut tally = Tally::new();
|
||||
let ask = tally.ask("export");
|
||||
assert_eq!(tally.restart(), vec!["export"]);
|
||||
// Its late answer finds nobody waiting.
|
||||
assert_eq!(tally.answered(ask), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_newest_text_counts() {
|
||||
let mut tally = Tally::<()>::new();
|
||||
tally.change(2);
|
||||
assert!(tally.text(2));
|
||||
assert!(!tally.text(1), "an older text is dropped");
|
||||
assert!(tally.settled());
|
||||
}
|
||||
}
|
||||
+70
-37
@@ -43,14 +43,10 @@ pub struct Failed(pub String);
|
||||
pub struct MarkView {
|
||||
pub(crate) web: Entity<WebView>,
|
||||
doc: Doc,
|
||||
/// The newest change the page announced.
|
||||
pub changed: u64,
|
||||
/// The change whose text `doc` holds.
|
||||
reported: u64,
|
||||
/// Page loads, changes and owed renders.
|
||||
tally: crate::tally::Tally<Rendered>,
|
||||
/// The mark at the last load or save.
|
||||
saved: String,
|
||||
/// Page loads so far; a reload restarts its versions.
|
||||
load: u64,
|
||||
pub mode: Mode,
|
||||
/// Headings as the shown editor reads them.
|
||||
pub outline: Vec<Heading>,
|
||||
@@ -70,9 +66,6 @@ pub struct MarkView {
|
||||
pub place: Option<Place>,
|
||||
/// The writing view, as the page applies it (MARK-09).
|
||||
look: Value,
|
||||
/// Renders asked of the page, by number.
|
||||
renders: std::collections::HashMap<u64, Rendered>,
|
||||
asked: u64,
|
||||
pub focus: FocusHandle,
|
||||
}
|
||||
|
||||
@@ -95,10 +88,8 @@ impl MarkView {
|
||||
MarkView {
|
||||
web,
|
||||
doc,
|
||||
changed: 0,
|
||||
reported: 0,
|
||||
tally: crate::tally::Tally::new(),
|
||||
saved,
|
||||
load: 0,
|
||||
mode: if large { Mode::Source } else { Mode::Visual },
|
||||
large,
|
||||
outline: Vec::new(),
|
||||
@@ -108,8 +99,6 @@ impl MarkView {
|
||||
attachable: false,
|
||||
place: None,
|
||||
look: Value::Null,
|
||||
renders: std::collections::HashMap::new(),
|
||||
asked: 0,
|
||||
folder,
|
||||
focus,
|
||||
}
|
||||
@@ -117,7 +106,12 @@ impl MarkView {
|
||||
|
||||
/// Every change's text has arrived.
|
||||
pub fn settled(&self) -> bool {
|
||||
self.reported == self.changed
|
||||
self.tally.settled()
|
||||
}
|
||||
|
||||
/// The newest change the page announced.
|
||||
pub fn changed(&self) -> u64 {
|
||||
self.tally.changed()
|
||||
}
|
||||
|
||||
/// The text's size now, in bytes.
|
||||
@@ -190,8 +184,10 @@ impl MarkView {
|
||||
doc.source = source;
|
||||
}
|
||||
self.saved = mark(&text);
|
||||
(self.changed, self.reported) = (0, 0);
|
||||
crate::page::call(self.web.read(cx), "refresh", &Value::from(text));
|
||||
// Reports and renders from before belong to the old text.
|
||||
let lapsed = self.tally.restart();
|
||||
self.end_renders(lapsed, "the text was replaced from disk", cx);
|
||||
crate::page::refresh(self.web.read(cx), &text, self.tally.load());
|
||||
cx.emit(Reported);
|
||||
}
|
||||
|
||||
@@ -213,9 +209,8 @@ impl MarkView {
|
||||
done: impl FnOnce(Result<Value, String>, &mut gpui::App) + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.asked += 1;
|
||||
self.renders.insert(self.asked, Box::new(done));
|
||||
let ask = serde_json::json!({ "kind": kind, "ask": self.asked });
|
||||
let ask = self.tally.ask(Box::new(done));
|
||||
let ask = serde_json::json!({ "kind": kind, "ask": ask });
|
||||
crate::page::call(self.web.read(cx), "render", &ask);
|
||||
}
|
||||
|
||||
@@ -240,7 +235,7 @@ impl MarkView {
|
||||
drop(doc);
|
||||
let mode = if self.large { Mode::Source } else { mode };
|
||||
self.mode = mode;
|
||||
let at = (mode, self.load, place);
|
||||
let at = (mode, self.tally.load(), place);
|
||||
crate::page::open(self.web.read(cx), ¤t, at, &self.folder, cx);
|
||||
cx.emit(Reported);
|
||||
}
|
||||
@@ -262,23 +257,50 @@ impl MarkView {
|
||||
match event {
|
||||
// A reload shows the last text that arrived.
|
||||
Event::Loaded => {
|
||||
self.load += 1;
|
||||
let lapsed = self.tally.restart();
|
||||
self.end_renders(lapsed, "the page reloaded", cx);
|
||||
self.shown = false;
|
||||
(self.changed, self.reported) = (0, 0);
|
||||
let text = self.doc.lock().expect("the text lock").current.clone();
|
||||
let at = (self.mode, self.load, self.place);
|
||||
let at = (self.mode, self.tally.load(), self.place);
|
||||
crate::page::open(web.read(cx), &text, at, &self.folder, cx);
|
||||
cx.emit(Reported);
|
||||
}
|
||||
Event::Query(said, reply) => self.query(said, reply, cx),
|
||||
Event::Leave(url) => log::info!("mark: a link out was not followed: {url}"),
|
||||
Event::Failed(why) => cx.emit(Failed(why.clone())),
|
||||
// A click on a link: web and mail open outside.
|
||||
Event::Leave(url) if ely_office::outward(url) => {
|
||||
log::info!("mark: opens a link outside");
|
||||
cx.open_url(url);
|
||||
}
|
||||
Event::Leave(_) => {
|
||||
log::info!("mark: a link of another kind was refused");
|
||||
let said = ely_ui::i18n::text(cx, "office.link.refused", &[]).to_string();
|
||||
cx.emit(Failed(said));
|
||||
}
|
||||
Event::Failed(why) => {
|
||||
let lapsed = self.tally.lapse();
|
||||
self.end_renders(lapsed, why, cx);
|
||||
cx.emit(Failed(why.clone()));
|
||||
}
|
||||
Event::Printed(path, _) => {
|
||||
log::error!("mark: an editor page printed {}", path.display())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the old page will never answer end with `why`.
|
||||
fn end_renders(&mut self, pending: Vec<Rendered>, why: &str, cx: &mut Context<Self>) {
|
||||
if pending.is_empty() {
|
||||
return;
|
||||
}
|
||||
log::warn!("mark: {} renders lost with the page", pending.len());
|
||||
let why = why.to_string();
|
||||
cx.defer(move |cx| {
|
||||
for done in pending {
|
||||
done(Err(why.clone()), cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn query(&mut self, said: &str, reply: &Reply, cx: &mut Context<Self>) {
|
||||
let said: Value = match serde_json::from_str(said) {
|
||||
Ok(said) => said,
|
||||
@@ -287,10 +309,7 @@ impl MarkView {
|
||||
return reply.fail("bad JSON");
|
||||
}
|
||||
};
|
||||
if said["generation"]
|
||||
.as_u64()
|
||||
.is_some_and(|load| load != self.load)
|
||||
{
|
||||
if !self.tally.current(said["generation"].as_u64()) {
|
||||
log::info!("mark: a report from an earlier page load was dropped");
|
||||
return reply.ok("");
|
||||
}
|
||||
@@ -307,6 +326,10 @@ impl MarkView {
|
||||
self.mode = mode;
|
||||
cx.notify();
|
||||
}
|
||||
if let Some(what) = said["broke"].as_str() {
|
||||
log::error!("mark: the visual editor broke ({what}); source shows");
|
||||
cx.emit(Failed(ely_ui::i18n::text(cx, "mark.visual.broke", &[("what", what)]).to_string()));
|
||||
}
|
||||
self.shown = true;
|
||||
if let Some(place) = self.waiting.take() {
|
||||
crate::page::call(self.web.read(cx), "reveal", &place);
|
||||
@@ -316,6 +339,14 @@ impl MarkView {
|
||||
}
|
||||
cx.emit(Reported);
|
||||
}
|
||||
(Some("failed"), ..) => {
|
||||
let Some(what) = said["what"].as_str() else {
|
||||
log::error!("mark: the page sent a failure without its name");
|
||||
return reply.fail("bad failure");
|
||||
};
|
||||
log::error!("mark: the page built no editor ({what})");
|
||||
cx.emit(Failed(ely_ui::i18n::text(cx, "mark.page.broke", &[("what", what)]).to_string()));
|
||||
}
|
||||
(Some("outline"), ..) => match headings(&said["items"]) {
|
||||
Some(outline) => {
|
||||
self.outline = outline;
|
||||
@@ -351,7 +382,7 @@ impl MarkView {
|
||||
(Some("table" | "inserted"), ..) => {}
|
||||
(Some("rendered"), ..) => {
|
||||
let ask = said["ask"].as_u64().unwrap_or_default();
|
||||
let Some(done) = self.renders.remove(&ask) else {
|
||||
let Some(done) = self.tally.answered(ask) else {
|
||||
log::error!("mark: a render nobody asked for came back");
|
||||
return reply.fail("unasked render");
|
||||
};
|
||||
@@ -379,16 +410,18 @@ impl MarkView {
|
||||
}
|
||||
},
|
||||
(Some("changed"), Some(version), _) => {
|
||||
self.changed = self.changed.max(version);
|
||||
self.tally.change(version);
|
||||
cx.emit(Reported);
|
||||
}
|
||||
// Only the newest text counts.
|
||||
(Some("text"), Some(version), Some(text)) if version >= self.reported => {
|
||||
self.doc.lock().expect("the text lock").current = text.to_owned();
|
||||
self.reported = version;
|
||||
cx.emit(Reported);
|
||||
(Some("text"), Some(version), Some(text)) => {
|
||||
if !self.tally.text(version) {
|
||||
log::info!("mark: an older text was dropped");
|
||||
} else {
|
||||
self.doc.lock().expect("the text lock").current = text.to_owned();
|
||||
cx.emit(Reported);
|
||||
}
|
||||
}
|
||||
(Some("text"), Some(_), Some(_)) => log::info!("mark: an older text was dropped"),
|
||||
_ => {
|
||||
log::error!("mark: the page asked something unknown");
|
||||
return reply.fail("unknown request");
|
||||
|
||||
Reference in new issue
Block a user