Edit titles in their boxes, with vim keys inside the text
Titles are typed right in their boxes on the canvas: the box grows with the text, the terminal's cursor sits in it (a bar while typing, a block on the letter with vim keys), and the border turns the insert color. `i`, `A` and Enter edit at the end, `I` at the start, `cc` and `S` type the title anew. The document follows the text as it's typed, so the layout stays current, and the whole edit is one undo step. Esc gives vim's normal mode inside the text (TextEditor, in ui/): h l w b e 0 ^ $, f t F T with ; and ,, the d c y operators with any of those motions or the iw aw i" a" i( a( text objects, x X s S D C Y, r ~, p P, counts, u and Ctrl-r for the edit's own changes, and `.` for the last one, typing included. j k gg G and J work on lines, for bodies. It follows vim where it's quirky too: cw acts like ce, dw stops at the end of the line, Esc steps back onto the last letter. Keys have two new tables, `text` and `insert`, and in text an operator takes motions as targets (`dw`, `c$`, `2d3fx`), which which-key lists. Rapid entry: after `o`, `O` or `a`, the new idea's title is typed at once; Enter starts the next idea, Tab puts the one being typed under the idea above, Shift-Tab takes it out a level, and Enter on an empty idea stops. An idea left empty goes away without a trace in the undo history (a group whose edits cancel out leaves no step); a title left empty comes back as it was. Inside a title, `o` and `O` start a new sibling. Registers are shared by the whole app, so text yanked in one title pastes into another; `"a`-`"z` and `"_` work, and `"+` stays inside cumulus until the app core connects the clipboard. The mouse while editing: a click in the text moves the text cursor, a click anywhere else keeps the text and finishes the edit, and a double-click on a title starts editing at that letter. `>>` and `<<` now move nodes: under the node above, as its last child, or out a level, right after the old parent, with counts taking in the next siblings. The command line's input became TextInput, line-aware so the editor can use it for bodies too; bodies themselves get edited in the Note view. The example's tests type every key above into the app and check the text and the cursor after each one against what vim does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6eedbe0ce2
commit
84aefbecdb
31 files changed
+3289
-341
No files matched your search
@@ -26,6 +26,14 @@ pub enum Motion {
|
||||
First,
|
||||
/// The last node (in text: the end).
|
||||
Last,
|
||||
/// The end of the word (`e`, in text).
|
||||
WordEnd,
|
||||
/// The start of the line (`0`, in text).
|
||||
LineStart,
|
||||
/// The line's first character that isn't a space (`^`, in text).
|
||||
FirstNonBlank,
|
||||
/// The end of the line (`$`, in text).
|
||||
LineEnd,
|
||||
SearchNext,
|
||||
SearchPrevious,
|
||||
/// To the next time a character shows up on the line (`f`, in text).
|
||||
@@ -36,6 +44,10 @@ pub enum Motion {
|
||||
TillForward(char),
|
||||
/// To just after the previous time a character shows up (`T`).
|
||||
TillBackward(char),
|
||||
/// The last `f`, `F`, `t` or `T` again (`;`).
|
||||
RepeatFind,
|
||||
/// The last `f`, `F`, `t` or `T` again, the other way (`,`).
|
||||
RepeatFindBackward,
|
||||
}
|
||||
|
||||
/// Keys that wait for a target, like vim's `d`: `dd`, `dab`.
|
||||
@@ -75,6 +87,22 @@ pub enum Target {
|
||||
Selection,
|
||||
/// From the cursor to where a motion goes (`dw`, `dfx`, in text).
|
||||
Motion(Motion),
|
||||
/// What's inside a piece of text (`iw`, `i"`, `i(`).
|
||||
Inside(TextObject),
|
||||
/// A piece of text with its edges (`aw`: the word and a space, `a"`:
|
||||
/// the quotes too).
|
||||
Around(TextObject),
|
||||
}
|
||||
|
||||
/// Pieces of text an operator can act on wherever the cursor is in them, as
|
||||
/// in vim: `ciw` changes the word under the cursor.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum TextObject {
|
||||
Word,
|
||||
/// Text in double quotes.
|
||||
Quotes,
|
||||
/// Text in round brackets.
|
||||
Parens,
|
||||
}
|
||||
|
||||
/// Keys that need one more character, typed right after them: `m` then a
|
||||
@@ -87,6 +115,8 @@ pub enum CharKey {
|
||||
FindBackward,
|
||||
TillForward,
|
||||
TillBackward,
|
||||
/// `r`, in text: the character to put in place of the cursor's.
|
||||
Replace,
|
||||
}
|
||||
|
||||
impl CharKey {
|
||||
@@ -99,9 +129,19 @@ impl CharKey {
|
||||
CharKey::FindBackward => Command::Move(Motion::FindBackward(c)),
|
||||
CharKey::TillForward => Command::Move(Motion::TillForward(c)),
|
||||
CharKey::TillBackward => Command::Move(Motion::TillBackward(c)),
|
||||
CharKey::Replace => Command::ReplaceChar(c),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the key moves the cursor (`f`, `t`…), so it can be an
|
||||
/// operator's target too: `dfx`.
|
||||
pub fn moves(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
CharKey::FindForward | CharKey::FindBackward | CharKey::TillForward | CharKey::TillBackward
|
||||
)
|
||||
}
|
||||
|
||||
/// As the target of an operator, once the character is known (`dfx`).
|
||||
/// Only motions can be targets.
|
||||
pub fn target(self, c: char) -> Option<Target> {
|
||||
@@ -118,6 +158,7 @@ impl CharKey {
|
||||
CharKey::JumpToMark => {
|
||||
&[("a–z A–Z", "go to that mark"), ("'", "back to where you were before the last jump")]
|
||||
}
|
||||
CharKey::Replace => &[("any key", "the character to put in its place")],
|
||||
_ => &[("any key", "the character to go to")],
|
||||
}
|
||||
}
|
||||
@@ -214,6 +255,32 @@ pub enum Command {
|
||||
Redo,
|
||||
Repeat,
|
||||
|
||||
// Vim keys inside text.
|
||||
/// `i`: type before the cursor.
|
||||
InsertBefore,
|
||||
/// `a`: type after the cursor.
|
||||
InsertAfter,
|
||||
/// `I`: type at the start of the line.
|
||||
InsertAtStart,
|
||||
/// `A`: type at the end of the line.
|
||||
InsertAtEnd,
|
||||
/// `r`: this character in place of the cursor's.
|
||||
ReplaceChar(char),
|
||||
/// `~`: upper case to lower case and back.
|
||||
SwitchCase,
|
||||
/// `J`: the next line joins this one.
|
||||
JoinLines,
|
||||
|
||||
// Typing.
|
||||
/// Backspace.
|
||||
DeleteBefore,
|
||||
/// Delete.
|
||||
DeleteAfter,
|
||||
/// `Ctrl-w`.
|
||||
DeleteWordBefore,
|
||||
/// `Ctrl-u`.
|
||||
DeleteLineBefore,
|
||||
|
||||
// Looks, tags, portals.
|
||||
PickColor,
|
||||
PickStyle,
|
||||
@@ -331,6 +398,12 @@ pub const COMMANDS: &[CommandInfo] = &[
|
||||
motion("previous", M::Previous, "the previous node, in reading order"),
|
||||
motion("first", M::First, "the root"),
|
||||
motion("last", M::Last, "the last node"),
|
||||
motion("word-end", M::WordEnd, "the end of the word"),
|
||||
motion("line-start", M::LineStart, "the start of the line"),
|
||||
motion("first-non-blank", M::FirstNonBlank, "the first character of the line that isn't a space"),
|
||||
motion("line-end", M::LineEnd, "the end of the line"),
|
||||
motion("repeat-find", M::RepeatFind, "the last f / t again"),
|
||||
motion("repeat-find-backward", M::RepeatFindBackward, "the last f / t again, the other way"),
|
||||
motion("search-next", M::SearchNext, "the next match"),
|
||||
motion("search-previous", M::SearchPrevious, "the previous match"),
|
||||
with_char("find-forward", CharKey::FindForward, "to a character"),
|
||||
@@ -390,6 +463,21 @@ pub const COMMANDS: &[CommandInfo] = &[
|
||||
info("undo", C::Undo, "undo"),
|
||||
info("redo", C::Redo, "redo"),
|
||||
info("repeat", C::Repeat, "repeat the last change"),
|
||||
info("indent-node", C::Operate(Op::Indent, Target::Current), "under the node above"),
|
||||
info("outdent-node", C::Operate(Op::Outdent, Target::Current), "out from under its parent"),
|
||||
// Vim keys inside text.
|
||||
info("insert", C::InsertBefore, "type before the cursor"),
|
||||
info("append", C::InsertAfter, "type after the cursor"),
|
||||
info("insert-at-start", C::InsertAtStart, "type at the start of the line"),
|
||||
info("append-at-end", C::InsertAtEnd, "type at the end of the line"),
|
||||
with_char("replace-char", CharKey::Replace, "replace the character"),
|
||||
info("switch-case", C::SwitchCase, "switch upper and lower case"),
|
||||
info("join-lines", C::JoinLines, "join the next line to this one"),
|
||||
// Typing.
|
||||
info("delete-before", C::DeleteBefore, "delete the character before the cursor"),
|
||||
info("delete-after", C::DeleteAfter, "delete the character after the cursor"),
|
||||
info("delete-word-before", C::DeleteWordBefore, "delete the word before the cursor"),
|
||||
info("delete-line-before", C::DeleteLineBefore, "delete back to the start of the line"),
|
||||
// Looks, tags, portals.
|
||||
info("pick-color", C::PickColor, "color…"),
|
||||
info("pick-style", C::PickStyle, "style…"),
|
||||
|
||||
Reference in new issue
Block a user