Zotero: a pending citation no longer edits a replacement document (#470) (#580)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
MarekSpodBiedryandBrandon Thomas authored and GitHub committed 2026-10-11 22:20:33 -04:00
1 parent cfb65364c7
commit 387873ae05
2 files changed
+34 -2

No files matched your search

+31 -1
View File
@@ -38,6 +38,8 @@ pub struct ZoteroLink {
alert: Option<Alert>,
/// The command in flight.
pub busy: Option<Command>,
/// The document the command in flight started in; its calls are refused once another is open.
document: u64,
/// How the last command ended.
pub last: Option<Value>,
/// Zotero's calls in the command in flight (logged when it ends).
@@ -96,6 +98,7 @@ fn start(app: &mut WordApp, cmd: Command, ctx: Option<egui::Context>) -> Result<
if let Some(b) = app.zotero.busy {
return Err(format!("Zotero is still working on {}; finish in Zotero's window first", b.wire_name()));
}
let document = app.session.document_id();
let (call_tx, call_rx) = channel::<Pending>();
let (done_tx, done_rx) = channel();
let wake = ctx.clone();
@@ -123,6 +126,7 @@ fn start(app: &mut WordApp, cmd: Command, ctx: Option<egui::Context>) -> Result<
z.calls = Some(call_rx);
z.done = Some(done_rx);
z.busy = Some(cmd);
z.document = document;
log::info!("Zotero: {} started", cmd.wire_name());
app.status(tl!("Waiting for Zotero…"));
Ok(json!({"started": cmd.wire_name()}))
@@ -156,7 +160,7 @@ pub fn poll(app: &mut WordApp, ctx: &egui::Context) {
// One alert at a time: Zotero sends nothing else while it waits for the answer.
while app.zotero.alert.is_none() {
let Some(p) = app.zotero.calls.as_ref().and_then(|rx| rx.try_recv().ok()) else { break };
if p.call.method == "Document_displayAlert" {
if p.call.method == "Document_displayAlert" && app.session.document_id() == app.zotero.document {
let text = p.call.args.get(1).and_then(Value::as_str).unwrap_or("").to_string();
let buttons = p.call.args.get(3).and_then(Value::as_i64).unwrap_or(0);
app.zotero.methods.push(p.call.method.clone());
@@ -164,6 +168,11 @@ pub fn poll(app: &mut WordApp, ctx: &egui::Context) {
continue;
}
app.zotero.methods.push(p.call.method.clone());
if app.session.document_id() != app.zotero.document {
// New or Open replaced the document this command started in: its edits must not land in the new one.
let _ = p.reply.send(Err("WordCraft replaced the document this command started in".into()));
continue;
}
let rev = app.session.rev();
let mut host = AppHost { ctx };
let r = app.zotero.bridge.handle(&mut app.session, &mut host, &p.call);
@@ -313,6 +322,27 @@ mod tests {
assert_eq!(app.session.doc.plain_text(StoryRef::Body), "Cite: ");
}
#[test]
fn a_pending_citation_does_not_insert_into_a_replacement_document() {
let (addr, server) = fake_zotero(vec![
json!(["Application_getActiveDocument", [3]]),
json!(["Document_insertField", [1, "ReferenceMark", 0]]),
json!(["Field_setCode", [1, 1, "ITEM CSL_CITATION {}"]]),
json!(["Field_setText", [1, 1, "(Sample, 2020)", false]]),
json!(["Document_complete", [1]]),
]);
let mut app = WordApp::new(Session::new(Document::new()), Services::default());
app.zotero.options.addr = addr;
let ctx = egui::Context::default();
app.ctx = Some(ctx.clone());
app.run("ui.zotero.addEditCitation", json!({})).unwrap();
app.run("file.new", json!({"template": "blank"})).unwrap();
pump(&mut app, &ctx, |a| a.zotero.busy.is_none());
server.join().unwrap();
assert_eq!(app.session.doc.plain_text(StoryRef::Body), "");
assert!(!app.session.can_undo(), "the old command left no undo step in the new document");
}
#[test]
fn no_zotero_is_a_clear_error() {
let free = TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap();
+3 -1
View File
@@ -21,7 +21,9 @@ Citations (each runs one of the commands below).
They run in the app (and through the control channel and MCP bridged to the app). A command
returns at once with `{"started": …}`; Zotero then works in its own window and WordCraft answers
it in the background. All changes from one command are one undo step.
it in the background. All changes from one command are one undo step. If New or Open replaces
the document while a command is running, Zotero's remaining calls for it get an error and the
new document is left as it is.
From a terminal, against a file: