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

+122
View File
@@ -0,0 +1,122 @@
//! The fields every app reads from the shared file.
use std::io;
use serde_json::{Map, Value};
use super::Store;
/// Light, dark, or as the system is.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Appearance {
#[default]
System,
Light,
Dark,
}
/// The interface language; System follows the OS.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Language {
#[default]
System,
English,
Chinese,
}
/// Interface motion; System follows the OS setting.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Motion {
#[default]
System,
Reduced,
Full,
}
pub const APPEARANCE: &str = "appearance";
pub const LANGUAGE: &str = "language";
pub const MOTION: &str = "motion";
pub const OMARCHY_SYNC: &str = "omarchy_sync";
pub const AUTHOR: &str = "author";
/// The shared settings; absent fields take their defaults.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Shared {
pub appearance: Appearance,
pub language: Language,
pub motion: Motion,
pub omarchy_sync: bool,
/// Signs comments and changes; empty means the account name.
pub author: String,
}
impl Default for Shared {
fn default() -> Self {
Self {
appearance: Appearance::System,
language: Language::System,
motion: Motion::System,
omarchy_sync: true,
author: String::new(),
}
}
}
impl Appearance {
pub const ALL: [(Self, &'static str); 3] = [(Self::System, "system"), (Self::Light, "light"), (Self::Dark, "dark")];
}
impl Language {
pub const ALL: [(Self, &'static str); 3] = [(Self::System, "system"), (Self::English, "en-US"), (Self::Chinese, "zh-CN")];
}
impl Motion {
pub const ALL: [(Self, &'static str); 3] = [(Self::System, "system"), (Self::Reduced, "reduce"), (Self::Full, "full")];
}
/// The stored word for `choice` among `all`.
pub fn word<T: PartialEq>(all: &[(T, &'static str)], choice: &T) -> &'static str {
all.iter().find(|(each, _)| each == choice).map(|(_, word)| *word).expect("every choice has a word")
}
impl Shared {
/// The shared file as read now.
pub fn load() -> io::Result<Self> {
Self::from_fields(&Store::shared()?.read()?)
}
/// Known fields, typed; a wrong value is an error.
pub fn from_fields(fields: &Map<String, Value>) -> io::Result<Self> {
let defaults = Self::default();
Ok(Self {
appearance: pick(fields, APPEARANCE, &Appearance::ALL)?.unwrap_or(defaults.appearance),
language: pick(fields, LANGUAGE, &Language::ALL)?.unwrap_or(defaults.language),
motion: pick(fields, MOTION, &Motion::ALL)?.unwrap_or(defaults.motion),
omarchy_sync: match fields.get(OMARCHY_SYNC) {
None => defaults.omarchy_sync,
Some(Value::Bool(on)) => *on,
Some(other) => return Err(wrong(OMARCHY_SYNC, other)),
},
author: match fields.get(AUTHOR) {
None => defaults.author,
Some(Value::String(name)) => name.trim().to_string(),
Some(other) => return Err(wrong(AUTHOR, other)),
},
})
}
}
fn pick<T: Copy>(fields: &Map<String, Value>, key: &str, all: &[(T, &'static str)]) -> io::Result<Option<T>> {
match fields.get(key) {
None => Ok(None),
Some(Value::String(word)) => match all.iter().find(|(_, each)| each == word) {
Some((choice, _)) => Ok(Some(*choice)),
None => Err(wrong(key, &Value::String(word.clone()))),
},
Some(other) => Err(wrong(key, other)),
}
}
fn wrong(key: &str, value: &Value) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, format!("settings: {key} cannot be {value}"))
}
+91
View File
@@ -0,0 +1,91 @@
use serde_json::json;
use super::{Store, VERSION};
fn store() -> (tempfile::TempDir, Store) {
let dir = tempfile::tempdir().unwrap();
let store = Store::at(dir.path().join("ElyOffice").join("settings.json"));
(dir, store)
}
#[test]
fn an_absent_file_is_empty_and_a_write_makes_it_versioned() {
let (_dir, store) = store();
assert!(store.read().unwrap().is_empty());
store.set("appearance", json!("dark")).unwrap();
let read = store.read().unwrap();
assert_eq!(read["appearance"], "dark");
assert_eq!(read["version"], VERSION);
}
#[test]
fn a_write_keeps_fields_another_app_wrote() {
let (_dir, store) = store();
std::fs::create_dir_all(store.path().parent().unwrap()).unwrap();
std::fs::write(store.path(), r#"{"version":1,"later":{"kept":true},"language":"en"}"#).unwrap();
store.set("language", json!("zh-CN")).unwrap();
let read = store.read().unwrap();
assert_eq!(read["language"], "zh-CN");
assert_eq!(read["later"], json!({"kept": true}), "unknown fields stay");
}
#[test]
fn two_writers_at_once_lose_nothing() {
let (_dir, store) = store();
let writers: Vec<_> = (0..8)
.map(|n| {
let store = store.clone();
std::thread::spawn(move || store.set(&format!("field{n}"), json!(n)).unwrap())
})
.collect();
for writer in writers {
writer.join().unwrap();
}
let read = store.read().unwrap();
assert!((0..8).all(|n| read[&format!("field{n}")] == n), "{read:?}");
}
#[test]
fn a_newer_or_broken_file_is_refused_and_left_alone() {
let (_dir, store) = store();
std::fs::create_dir_all(store.path().parent().unwrap()).unwrap();
for text in [r#"{"version":2}"#, r#"{"version":"1"}"#, "[1]", "{"] {
std::fs::write(store.path(), text).unwrap();
assert!(store.read().is_err(), "{text} is refused");
assert!(store.set("language", json!("en")).is_err(), "{text} is not overwritten");
assert_eq!(std::fs::read_to_string(store.path()).unwrap(), text);
}
}
#[test]
fn shared_fields_type_strictly_and_default_when_absent() {
use super::{Appearance, Language, Motion, Shared};
let fields = |text: &str| serde_json::from_str::<serde_json::Map<_, _>>(text).unwrap();
assert_eq!(Shared::from_fields(&fields("{}")).unwrap(), Shared::default());
let read = Shared::from_fields(&fields(
r#"{"appearance":"dark","language":"zh-CN","motion":"reduce","omarchy_sync":false,"author":" Ana "}"#,
))
.unwrap();
assert_eq!(
(read.appearance, read.language, read.motion, read.omarchy_sync, read.author.as_str()),
(Appearance::Dark, Language::Chinese, Motion::Reduced, false, "Ana")
);
for bad in [r#"{"appearance":"blue"}"#, r#"{"omarchy_sync":"yes"}"#, r#"{"author":3}"#] {
assert!(Shared::from_fields(&fields(bad)).is_err(), "{bad}");
}
}
#[test]
fn a_write_overtaken_while_waiting_leaves_the_file() {
let (_dir, store) = store();
store.set("author", json!("Ana")).unwrap();
let held = crate::lock::FileLock::take(&store.path().parent().unwrap().join(".settings.lock")).unwrap();
let waiting = {
let store = store.clone();
std::thread::spawn(move || store.set_if("author", json!("Bob"), || false).unwrap())
};
std::thread::sleep(std::time::Duration::from_millis(200));
drop(held);
assert!(!waiting.join().unwrap(), "the overtaken write is skipped");
assert_eq!(store.read().unwrap()["author"], "Ana");
}