The playground becomes app::App, and `cumulus` runs it: the main loop with terminal setup and a panic hook, and the startup rules (./ideas.cum, else ~/ideas.cum, made the first time; files and folders by name; `cumulus new`; --paths). The documents' files: - autosave a second after the latest change (`:set noautosave` for vim-style :w), [+] in the document bar for unsaved changes; quitting saves first; without autosave :q refuses, :q! drops, ZZ saves first; - a file changed outside cumulus is read again as an undo step; with unsaved changes here too, cumulus asks which to keep; a file with a mistake in it holds autosave until it's fixed or :w saves over it; a deleted one waits for :w; nothing is reloaded under an edit; - the day's first save backs the file up to the data folder (30 days); - a document open in another cumulus is read-only ([RO]) until that one lets go: OS file locks in the data folder, so a crash leaves none; - where you left off (cursor, zoom, scroll), the recent documents (now in the documents picker) and the command history, in memory.ron; - :e (alone: read it again; ! drops changes), :new, :bd, :w, :wa, :x, :qa, <Space>i for :in, and :set for every setting. glyphs=ascii now reaches the frames and bars too, not just the canvas and outline. Stage 8 comes in four parts with a commit each (the user's choice); 8b is the settings files. The example is now a playground running the app on copies of the sample documents in a temporary folder, and the end-to-end tests moved to src/app/tests.rs, with tests for the files. Co-Authored-By: Claude Opus 5.5 <[email protected]>
48 lines
1.6 KiB
Rust
48 lines
1.6 KiB
Rust
//! Noticing that something else changed a file: cumulus remembers each
|
|
//! document file's [`Stamp`] when it reads or writes it, and looks again
|
|
//! every second. A different stamp means another program (an editor, a
|
|
//! sync tool, another cumulus) wrote the file, and cumulus reloads it.
|
|
//! Looking costs one quick question to the file system per document, so
|
|
//! there's no need for a file-watching library.
|
|
|
|
use std::fs;
|
|
use std::io;
|
|
use std::path::Path;
|
|
use std::time::SystemTime;
|
|
|
|
/// When a file was last written, and how long it is.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct Stamp {
|
|
modified: SystemTime,
|
|
len: u64,
|
|
}
|
|
|
|
impl Stamp {
|
|
/// The stamp of the file at `path`, or `None` when there's no file there.
|
|
pub fn of(path: &Path) -> io::Result<Option<Stamp>> {
|
|
match fs::metadata(path) {
|
|
Ok(metadata) => Ok(Some(Stamp { modified: metadata.modified()?, len: metadata.len() })),
|
|
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
|
|
Err(error) => Err(error),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::storage::test_support::TempDir;
|
|
|
|
#[test]
|
|
fn a_write_changes_the_stamp() {
|
|
let folder = TempDir::new();
|
|
let path = folder.path().join("garden.cum");
|
|
assert_eq!(Stamp::of(&path).unwrap(), None);
|
|
fs::write(&path, "one").unwrap();
|
|
let first = Stamp::of(&path).unwrap();
|
|
assert_eq!(Stamp::of(&path).unwrap(), first, "nothing changed");
|
|
fs::write(&path, "three").unwrap();
|
|
assert_ne!(Stamp::of(&path).unwrap(), first);
|
|
}
|
|
}
|