Ely Office: six offline-first desktop apps in Rust and GPUI

ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-08 21:20:08 -04:00
commit b7fca0146d
817 files changed
+143101

No files matched your search

+362
View File
@@ -0,0 +1,362 @@
use super::input::FODS;
use super::*;
/// How long the kernel may take to answer.
const SETTLE: std::time::Duration = std::time::Duration::from_secs(3);
/// The FODS fixture, loaded and its cursor read.
fn open() -> (tempfile::TempDir, Worker, Doc, Events) {
let install = install().expect("the pinned LOK build");
let dir = tempfile::tempdir().expect("a temp dir");
let mut worker = Worker::spawn(&install, &dir.path().join("profile")).expect("the worker");
let source = dir.path().join("in.fods");
write(&source, FODS);
let doc = worker
.load(&format!("file://{}", source.display()))
.expect("the sheet loads");
let seen = poll_until(&mut worker, &doc, &Events::default(), SETTLE, |seen| {
seen.cell.cursor.is_some()
});
(dir, worker, doc, seen)
}
fn type_text(worker: &mut Worker, doc: &Doc, text: &str) {
for c in text.chars() {
worker.post_key(doc, c as u32, 0).expect("a key posts");
}
}
/// The cells of `range`, as the kernel copies them.
fn values(worker: &mut Worker, doc: &Doc, range: &str) -> String {
worker.run(doc, "cell.goto", &[range]).expect("the range");
doc.selection_text(worker).expect("the text")
}
#[test]
fn typing_a_formula_offers_functions_usage_and_marks() {
if install().is_none() {
eprintln!("SKIP: no pinned LOK build");
return;
}
let (_dir, mut worker, doc, seen) = open();
worker.post_key(&doc, 0, 1024).expect("Down to A2");
type_text(&mut worker, &doc, "=SU");
let offered = poll_until(&mut worker, &doc, &seen, SETTLE, |seen| {
seen.cell.functions.iter().any(|f| f.name == "SUMIF")
});
let sum = offered.cell.functions.iter().find(|f| f.name == "SUM");
assert!(
sum.is_some_and(|f| f.signature.contains(", ")),
"the signature takes the Excel separator: {sum:?}"
);
doc.complete_function(&mut worker, "SUMIF")
.expect("the completion");
let done = poll_until(&mut worker, &doc, &offered, SETTLE, |seen| {
seen.cell.formula == "=SUMIF()"
});
assert_eq!(done.cell.formula, "=SUMIF()", "the name completes");
type_text(&mut worker, &doc, "A1,");
let usage = poll_until(&mut worker, &doc, &done, SETTLE, |seen| {
seen.cell.formula == "=SUMIF(A1,)" && seen.cell.usage.contains('►')
});
assert_eq!(usage.cell.formula, "=SUMIF(A1,)", "typing goes on inside");
assert!(
usage.cell.usage.contains("► Criteria"),
"{}",
usage.cell.usage
);
assert!(usage.cell.functions.is_empty(), "the list gives way");
let b3 = usage.cell.cursor.expect("the cell cursor");
let (x, y) = (b3.0 + b3.2 * 3 / 2, b3.1 + b3.3 * 3 / 2);
for kind in [0, 1] {
let click = Mouse {
kind,
x,
y,
count: 1,
buttons: 1,
modifiers: 0,
};
worker.post_mouse(&doc, click).expect("the click posts");
}
let picked = poll_until(&mut worker, &doc, &usage, SETTLE, |seen| {
seen.cell.formula == "=SUMIF(A1,B3)" && !seen.cell.marks.is_empty()
});
assert_eq!(
picked.cell.formula, "=SUMIF(A1,B3)",
"a click picks the cell"
);
assert_eq!(picked.cell.marks.len(), 1, "the picked cell is marked");
assert_eq!(picked.cell.marks[0].part, 0);
type_text(&mut worker, &doc, "*2");
let typed = poll_until(&mut worker, &doc, &picked, SETTLE, |seen| {
seen.cell.formula == "=SUMIF(A1,B3*2)"
});
assert_eq!(
typed.cell.formula, "=SUMIF(A1,B3*2)",
"typing goes on after a pick"
);
let picked = typed;
// The first Escape may only close the kernel's hint.
worker.post_key(&doc, 0, 1281).expect("Escape");
worker.post_key(&doc, 0, 1281).expect("Escape cancels");
let done = poll_until(&mut worker, &doc, &picked, SETTLE, |seen| {
seen.cell.marks.is_empty() && seen.cell.formula.is_empty()
});
assert!(done.cell.marks.is_empty(), "{:?}", done.cell);
worker
.run(&doc, "cell.enter", &["=A1+B3"])
.expect("a formula");
worker.run(&doc, "cell.goto", &["A2"]).expect("back to it");
worker.post_key(&doc, 0, 769).expect("F2 edits it");
let edited = poll_until(&mut worker, &doc, &done, SETTLE, |seen| {
seen.cell.marks.len() == 2
});
assert_eq!(edited.cell.marks.len(), 2, "each reference is marked");
worker.post_key(&doc, 0, 1280).expect("Enter commits");
let entered = poll_until(&mut worker, &doc, &edited, SETTLE, |seen| {
seen.cell.marks.is_empty()
});
assert!(entered.cell.marks.is_empty(), "the commit clears the marks");
}
#[test]
fn a_broken_formula_enters_as_typed_without_a_dialog() {
if install().is_none() {
eprintln!("SKIP: no pinned LOK build");
return;
}
let (_dir, mut worker, doc, seen) = open();
worker.run(&doc, "cell.goto", &["C1"]).expect("C1");
type_text(&mut worker, &doc, "=1+");
worker.post_key(&doc, 0, 1280).expect("Enter");
let entered = poll_until(&mut worker, &doc, &seen, SETTLE, |seen| {
seen.cell.address == "C2"
});
assert!(
entered.blocked.is_empty(),
"no dialog: {:?}",
entered.blocked
);
assert!(values(&mut worker, &doc, "C1").starts_with("Err:"));
}
#[test]
fn formulas_take_excel_syntax() {
if install().is_none() {
eprintln!("SKIP: no pinned LOK build");
return;
}
let (_dir, mut worker, doc, _) = open();
let sheet = doc.sheets(&mut worker).expect("the sheets").list[0]
.name
.clone();
for (cell, formula) in [
("D1", "=SUM(1,2)"),
("D2", &format!("='{sheet}'!D1*2")),
("D3", "=SUM($D$1:D2,1)"),
] {
worker.run(&doc, "cell.goto", &[cell]).expect("the cell");
worker
.run(&doc, "cell.enter", &[formula])
.expect("the entry");
}
assert_eq!(values(&mut worker, &doc, "D1:D3"), "3\n6\n10\n");
}
/// Enters each formula at its cell.
fn enter(worker: &mut Worker, doc: &Doc, cells: &[(&str, &str)]) {
for (cell, formula) in cells {
worker.run(doc, "cell.goto", &[cell]).expect("the cell");
worker
.run(doc, "cell.enter", &[formula])
.expect("the entry");
}
}
#[test]
fn the_kernel_computes_each_family_of_functions() {
if install().is_none() {
eprintln!("SKIP: no pinned LOK build");
return;
}
let (_dir, mut worker, doc, _) = open();
enter(
&mut worker,
&doc,
&[
("F1", "3"),
("F2", "1"),
("F3", "2"),
("F4", "3"),
("G1", "=F1^2+F2*4-F3/2"),
("G2", "=IF(AND(F1>2,NOT(F2=2)),\"yes\",\"no\")"),
("G3", "=COUNTIF(F1:F4,3)+SUMIFS(F1:F4,F1:F4,\">1\")"),
(
"G4",
"=CONCAT(LEFT(\"Ely Office\",3),\"-\",TEXT(0.25,\"0%\"))",
),
(
"G5",
"=DATEDIF(DATE(2026,1,31),EDATE(DATE(2026,1,31),1),\"d\")",
),
(
"G6",
"=XLOOKUP(2,F2:F4,F2:F4*10)+INDEX(F1:F4,MATCH(1,F1:F4,0))",
),
("G7", "=SUM(SORT(UNIQUE(F1:F4),1,-1))"),
("G8", "=ROWS(FILTER(F1:F4,F1:F4>1))*SUM(SEQUENCE(3))"),
],
);
assert_eq!(
values(&mut worker, &doc, "G1:G8"),
"12\nyes\n10\nEly-25%\n28\n21\n6\n18\n"
);
}
#[test]
fn manual_calculation_waits_and_survives_a_save() {
if install().is_none() {
eprintln!("SKIP: no pinned LOK build");
return;
}
let (dir, mut worker, doc, seen) = open();
enter(&mut worker, &doc, &[("F1", "1"), ("F2", "=F1*2")]);
assert_eq!(values(&mut worker, &doc, "F2"), "2");
worker.run(&doc, "calc.auto", &["false"]).expect("manual");
let manual = poll_until(&mut worker, &doc, &seen, SETTLE, |seen| {
seen.states
.contains(&(".uno:AutomaticCalculation".into(), "false".into()))
});
assert!(
manual
.states
.contains(&(".uno:AutomaticCalculation".into(), "false".into())),
"the state follows"
);
enter(&mut worker, &doc, &[("F1", "5")]);
assert_eq!(values(&mut worker, &doc, "F2"), "2", "the formula waits");
worker.run(&doc, "calc.now", &[]).expect("a recalc");
assert_eq!(values(&mut worker, &doc, "F2"), "10", "a recalc updates");
for format in ["ods", "xlsx", "xls", "fods"] {
let path = dir.path().join(format!("manual.{format}"));
doc.save_as(&mut worker, &crate::file_url(&path), format)
.expect("the save");
let again = worker
.load(&crate::file_url(&path))
.expect("the copy loads");
let state = poll_until(&mut worker, &again, &Events::default(), SETTLE, |seen| {
seen.states
.iter()
.any(|(name, _)| name == ".uno:AutomaticCalculation")
});
let auto = state
.states
.iter()
.rfind(|(name, _)| name == ".uno:AutomaticCalculation")
.map(|(_, value)| value.as_str());
assert_eq!(auto, Some("false"), "{format} keeps manual calculation");
worker.close_doc(again).expect("the copy closes");
}
}
#[test]
fn a_circular_reference_reports_its_error() {
if install().is_none() {
eprintln!("SKIP: no pinned LOK build");
return;
}
let (_dir, mut worker, doc, seen) = open();
enter(&mut worker, &doc, &[("F1", "=F2+1"), ("F2", "=F1")]);
worker.run(&doc, "cell.goto", &["F1"]).expect("F1");
let state = poll_until(&mut worker, &doc, &seen, SETTLE, |seen| {
seen.states
.iter()
.any(|(name, value)| name == ".uno:StateTableCell" && value.contains("Circular"))
});
assert!(
state
.states
.iter()
.any(|(_, value)| value == "Error: Circular reference"),
"{:?}",
state.states
);
assert_eq!(values(&mut worker, &doc, "F1"), "Err:522");
}
#[test]
fn names_are_defined_changed_removed_and_flag_breaks() {
if install().is_none() {
eprintln!("SKIP: no pinned LOK build");
return;
}
let (_dir, mut worker, doc, _) = open();
let sheet = doc.sheets(&mut worker).expect("the sheets").list[0]
.name
.clone();
let read = |worker: &mut Worker| {
crate::sheet::names(&worker.values(&doc, crate::sheet::NAMES).expect("names"))
.expect("the names parse")
};
enter(&mut worker, &doc, &[("F1", "2"), ("F2", "5")]);
let range = format!("'{sheet}'!$F$1");
worker
.run(&doc, "names.define", &["Rate", &range, ""])
.expect("a workbook name");
worker
.run(&doc, "names.define", &["Local", "$F$2", &sheet])
.expect("a sheet name");
enter(&mut worker, &doc, &[("G1", "=Rate*10+Local")]);
assert_eq!(values(&mut worker, &doc, "G1"), "25");
let names = read(&mut worker);
let rate = names.iter().find(|name| name.name == "Rate").expect("Rate");
assert_eq!((rate.scope.as_str(), rate.broken), ("", false));
assert!(
names
.iter()
.any(|name| name.name == "Local" && name.scope == sheet)
);
let moved = format!("'{sheet}'!$F$2");
worker
.run(&doc, "names.define", &["Rate", &moved, ""])
.expect("a new range");
assert_eq!(values(&mut worker, &doc, "G1"), "55", "the range moves");
worker.run(&doc, "edit.undo", &[]).expect("an undo");
assert_eq!(values(&mut worker, &doc, "G1"), "25", "the change undoes");
assert!(
worker
.run(&doc, "names.define", &["A1", "$F$1", ""])
.is_err(),
"a cell address is no name"
);
let check = |worker: &mut Worker, name: &str| {
let query = crate::sheet::name_check_query(name);
crate::sheet::name_check(&worker.values(&doc, &query).expect("a check"))
.expect("the check parses")
};
assert_eq!(check(&mut worker, "Tax_2026"), "valid");
assert_eq!(check(&mut worker, "B2"), "address");
assert_eq!(check(&mut worker, "a b&c"), "characters");
worker.run(&doc, "cell.goto", &["Rate"]).expect("a jump");
assert_eq!(
poll_until(&mut worker, &doc, &Events::default(), SETTLE, |seen| {
seen.cell.address == "Rate"
})
.cell
.address,
"Rate",
"the name box jumps to a name and shows it"
);
worker.run(&doc, "cell.goto", &["F1"]).expect("F1");
worker.run(&doc, "rows.delete", &[]).expect("row 1 goes");
let names = read(&mut worker);
assert!(
names.iter().any(|name| name.name == "Rate" && name.broken),
"a deleted target breaks the name: {names:?}"
);
worker
.run(&doc, "names.remove", &["Local", &sheet])
.expect("a removal");
assert!(!read(&mut worker).iter().any(|name| name.name == "Local"));
}