Run cumulus for real: the app core and its files (stage 8a)
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]>
This commit is contained in:
1 parent
e6f3c06ecc
commit
f6dc4564aa
47 files changed
+3985
-1024
No files matched your search
+67
-4
@@ -5,13 +5,14 @@
|
||||
//!
|
||||
//! [`Buffer::apply`]: super::buffer::Buffer::apply
|
||||
|
||||
use super::documents::BufferId;
|
||||
use super::panes::Arrange;
|
||||
use super::popup::{MenuFor, PickKind};
|
||||
use super::text_edit::StartEdit;
|
||||
use crate::command::parse::SetArg;
|
||||
use crate::input::command::Command;
|
||||
use crate::input::key::Key;
|
||||
use crate::model::{Direction, EditOp, Group, NodeId};
|
||||
use crate::model::{Direction, EditOp, Group, NodeId, Title};
|
||||
use crate::view::ViewKind;
|
||||
|
||||
/// How a status-bar message is meant.
|
||||
@@ -44,6 +45,15 @@ impl Message {
|
||||
pub fn problem(text: impl Into<String>) -> Message {
|
||||
Message { text: text.into(), tone: Tone::Problem }
|
||||
}
|
||||
|
||||
/// Several messages in one, with ` · ` between them: a problem if one of
|
||||
/// them is. `None` when there are none.
|
||||
pub fn joined(messages: Vec<Message>) -> Option<Message> {
|
||||
let problem = messages.iter().any(|message| message.tone == Tone::Problem);
|
||||
let tone = if problem { Tone::Problem } else { messages.first()?.tone };
|
||||
let texts: Vec<&str> = messages.iter().map(|message| message.text.as_str()).collect();
|
||||
Some(Message { text: texts.join(" · "), tone })
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the cursor goes after an edit.
|
||||
@@ -135,18 +145,71 @@ pub enum AppAction {
|
||||
},
|
||||
FocusPane(Direction),
|
||||
NextPane,
|
||||
/// Close the focused pane (the last one quits).
|
||||
ClosePane,
|
||||
/// Close the focused pane (`:q`, `ZZ`, `Ctrl-w c`). The last one quits,
|
||||
/// unless a document has unsaved changes; `force` (`:q!`) quits anyway.
|
||||
ClosePane {
|
||||
force: bool,
|
||||
},
|
||||
OnlyPane,
|
||||
EqualizePanes,
|
||||
/// The focused pane shows another view.
|
||||
ShowView(ViewKind),
|
||||
SwitchDocument(DocumentChoice),
|
||||
|
||||
Quit,
|
||||
// Files.
|
||||
/// Save (`:w`, `:x`, `:wa`).
|
||||
Save(SaveWhich),
|
||||
/// Open a document in the focused pane (`:e`), from a path as typed:
|
||||
/// relative to the focused document's folder, `~` for the home folder.
|
||||
/// No path reads the focused document again from its file; `force`
|
||||
/// (`:e!`) drops unsaved changes to do it.
|
||||
Edit {
|
||||
path: Option<String>,
|
||||
force: bool,
|
||||
},
|
||||
/// Make a new document next to the focused one, and show it (`:new`).
|
||||
NewDocument(Option<Title>),
|
||||
/// Close the focused document (`:bd`); `force` drops unsaved changes.
|
||||
CloseDocument {
|
||||
force: bool,
|
||||
},
|
||||
/// A file changed outside cumulus while its document had unsaved
|
||||
/// changes here: which version to keep.
|
||||
Resolve {
|
||||
buffer: BufferId,
|
||||
keep: Keep,
|
||||
},
|
||||
|
||||
/// Quit cumulus (`:qa`): not while a document has unsaved changes,
|
||||
/// unless `force` (`:qa!`).
|
||||
Quit {
|
||||
force: bool,
|
||||
},
|
||||
Say(Message),
|
||||
}
|
||||
|
||||
/// What `:w` and its kin save.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SaveWhich {
|
||||
/// The focused document, changed or not (`:w`).
|
||||
This,
|
||||
/// The focused document, if it has unsaved changes (`:x`, `ZZ`).
|
||||
IfChanged,
|
||||
/// Every document with unsaved changes (`:wa`).
|
||||
All,
|
||||
}
|
||||
|
||||
/// Which version of a document to keep when its file changed outside
|
||||
/// cumulus while it had unsaved changes here.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Keep {
|
||||
/// The one on screen: it's saved over the file.
|
||||
Mine,
|
||||
/// The file's: it takes the place of the one on screen (`u` brings that
|
||||
/// back).
|
||||
Theirs,
|
||||
}
|
||||
|
||||
/// Which document the focused pane switches to.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DocumentChoice {
|
||||
|
||||
Reference in new issue
Block a user