//! Everything a key can ask for. Views never see raw key presses: the engine //! turns them into [`Command`]s using the tables in //! [`bindings`](super::bindings), and each view decides what a command means //! there. `Move(Left)` goes to the nearest node on the left on the canvas, //! for example, and to the parent in the outline. //! //! [`COMMANDS`] names every command, for `keys.toml`, and says what it does, //! for which-key and the help. use crate::model::{Layout, Side}; /// Ways to move the cursor. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Motion { Left, Down, Up, Right, Parent, FirstChild, NextSibling, PreviousSibling, /// The next node in reading order (in text: the next word). Next, /// The previous node in reading order (in text: the previous word). Previous, /// The root (in text: the start). 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). FindForward(char), /// To the previous time a character shows up on the line (`F`). FindBackward(char), /// To just before the next time a character shows up (`t`). 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`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Operator { Delete, Yank, Change, Indent, Outdent, } impl Operator { /// What the operator does, for which-key's title. pub fn name(self) -> &'static str { match self { Operator::Delete => "delete", Operator::Yank => "yank", Operator::Change => "change", Operator::Indent => "indent", Operator::Outdent => "outdent", } } } /// What an operator acts on. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Target { /// The operator's own key again (`dd`, `yy`, `>>`): this node, with its /// branch (in text: this line). Current, /// `ab`, around a branch: the node and everything below it. Branch, /// `ib`, inside a branch: just what's below the node. Children, /// What's selected (an operator in visual mode). 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 /// letter. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum CharKey { SetMark, JumpToMark, FindForward, FindBackward, TillForward, TillBackward, /// `r`, in text: the character to put in place of the cursor's. Replace, } impl CharKey { /// The command once the character is known. pub fn command(self, c: char) -> Command { match self { CharKey::SetMark => Command::SetMark(c), CharKey::JumpToMark => Command::JumpToMark(c), CharKey::FindForward => Command::Move(Motion::FindForward(c)), 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 { match self.command(c) { Command::Move(motion) => Some(Target::Motion(motion)), _ => None, } } /// What the character is for, for which-key: the keys, and what they do. pub fn hints(self) -> &'static [(&'static str, &'static str)] { match self { CharKey::SetMark => &[("a–z", "mark this node"), ("A–Z", "mark it, across documents")], 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")], } } } /// Where text goes and comes from with yank and paste: `"a` before a /// command picks register `a`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Register { /// The usual one, when no register is named. Unnamed, /// `"a` to `"z` (capitals name the same registers). Named(char), /// `"+` (or `"*`): the system clipboard. Clipboard, /// `"_`: nowhere, for deleting without keeping a copy. BlackHole, } impl Register { /// The register a character names after `"`, if it names one. pub fn named(c: char) -> Option { match c { '"' => Some(Register::Unnamed), 'a'..='z' => Some(Register::Named(c)), 'A'..='Z' => Some(Register::Named(c.to_ascii_lowercase())), '+' | '*' => Some(Register::Clipboard), '_' => Some(Register::BlackHole), _ => None, } } /// The registers, for which-key after `"`. pub const HINTS: &[(&str, &str)] = &[ ("a–z", "a named register"), ("+", "the system clipboard"), ("_", "nowhere (delete without a copy)"), ]; } /// Everything a key sequence can ask for. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Command { /// Move the cursor. Move(Motion), /// An operator with its target: `dd`, `yab`. Operate(Operator, Target), /// Mark the cursor's node with a letter (`ma`). SetMark(char), /// Go to a marked node (`'a`). JumpToMark(char), // Jumping around. Search, JumpBack, JumpForward, FollowPortal, FollowLink, /// `gu`: up to where the document is reached from (the portals that /// lead to it). GoUp, /// Enter: edit the title, or go through a portal. Activate, // The view. Center, ScrollDown, ScrollUp, ScrollLeft, ScrollRight, HalfPageDown, HalfPageUp, ZoomIn, ZoomOut, ToggleFold, OpenFold, CloseFold, AutoFold, OpenAllFolds, CloseAllFolds, ToggleFocus, ShowBody, /// The outline: every body under its title, or none. ToggleBodies, // Creating and editing. AddBelow, AddAbove, AddChild, EditTitle, EditTitleAtStart, RetypeTitle, /// In the K card: type in the body, at its end. EditBody, /// In the K card: type in the body, at its start. EditBodyAtStart, /// `x`: delete just this node; its children move up. DeleteNode, /// Inside text: paste after the text cursor. PasteAfter, /// Inside text: paste before the text cursor. PasteBefore, /// `p` on nodes: paste as the node's last children. PasteAsChild, /// `P` on nodes: paste as the node's next siblings. PasteAsSibling, Undo, 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, // Arranging. /// Lay the cursor's branch out (or each selected one). Tidy(Layout), /// Line the selected nodes up. Align(Side), // Looks, tags, portals. PickColor, PickStyle, PickBorder, EditTags, NewPortal, Capture, // Modes. Visual, VisualBranches, MoveMode, ConnectMode, CommandLine, Escape, // In visual, move and connect modes. SwapEnds, FarLeft, FarDown, FarUp, FarRight, ToggleWhole, Confirm, RemoveLink, AttachAsChild, CycleArrows, CycleLine, EditLabel, // Panes and documents. SplitVertical, SplitHorizontal, FocusLeft, FocusDown, FocusUp, FocusRight, NextPane, ClosePane, OnlyPane, EqualizePanes, NextDocument, PreviousDocument, PickDocument, ShowCanvas, ShowOutline, ShowNote, // Help and the app. Help, ToggleWhichKey, /// `ZZ`: close the pane (everything is saved already). Close, } /// What a key sequence is bound to. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Bound { /// A command, run as soon as its keys are typed. Command(Command), /// An operator: it waits for a target. Operator(Operator), /// A key that waits for one more character. WithChar(CharKey), } /// What a key sequence typed after an operator means. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum TargetBound { Target(Target), WithChar(CharKey), } /// A command's name in `keys.toml`, and what it does. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CommandInfo { pub name: &'static str, pub bound: Bound, pub help: &'static str, } const fn info(name: &'static str, command: Command, help: &'static str) -> CommandInfo { CommandInfo { name, bound: Bound::Command(command), help } } const fn motion(name: &'static str, motion: Motion, help: &'static str) -> CommandInfo { info(name, Command::Move(motion), help) } const fn operator(name: &'static str, operator: Operator, help: &'static str) -> CommandInfo { CommandInfo { name, bound: Bound::Operator(operator), help } } const fn with_char(name: &'static str, key: CharKey, help: &'static str) -> CommandInfo { CommandInfo { name, bound: Bound::WithChar(key), help } } use Command as C; use Motion as M; use Operator as Op; /// Every command keys can be bound to, by name. A name in `keys.toml` is /// one of these. pub const COMMANDS: &[CommandInfo] = &[ // Moving the cursor. motion("left", M::Left, "left"), motion("down", M::Down, "down"), motion("up", M::Up, "up"), motion("right", M::Right, "right"), motion("parent", M::Parent, "the parent"), motion("first-child", M::FirstChild, "the first child"), motion("next-sibling", M::NextSibling, "the next sibling"), motion("previous-sibling", M::PreviousSibling, "the previous sibling"), motion("next", M::Next, "the next node, in reading order"), 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"), with_char("find-backward", CharKey::FindBackward, "back to a character"), with_char("till-forward", CharKey::TillForward, "up to a character"), with_char("till-backward", CharKey::TillBackward, "back up to a character"), with_char("set-mark", CharKey::SetMark, "mark this node"), with_char("jump-to-mark", CharKey::JumpToMark, "go to a mark"), info("search", C::Search, "search"), info("jump-back", C::JumpBack, "jump back"), info("jump-forward", C::JumpForward, "jump forward"), info("follow-portal", C::FollowPortal, "through the portal"), info("follow-link", C::FollowLink, "follow a cross-link"), info("go-up", C::GoUp, "up to the portals that lead here"), info("activate", C::Activate, "edit the title, or go through a portal"), // The view. info("center", C::Center, "center the view"), info("scroll-down", C::ScrollDown, "scroll down"), info("scroll-up", C::ScrollUp, "scroll up"), info("scroll-left", C::ScrollLeft, "scroll left"), info("scroll-right", C::ScrollRight, "scroll right"), info("half-page-down", C::HalfPageDown, "half a screen down"), info("half-page-up", C::HalfPageUp, "half a screen up"), info("zoom-in", C::ZoomIn, "zoom in"), info("zoom-out", C::ZoomOut, "zoom out"), info("fold-toggle", C::ToggleFold, "fold or unfold"), info("fold-open", C::OpenFold, "unfold"), info("fold-close", C::CloseFold, "fold"), info("fold-auto", C::AutoFold, "fold automatically (open while inside)"), info("fold-open-all", C::OpenAllFolds, "unfold everything"), info("fold-close-all", C::CloseAllFolds, "fold everything"), info("focus", C::ToggleFocus, "focus: fold the other branches"), info("show-body", C::ShowBody, "show the body"), info("toggle-bodies", C::ToggleBodies, "show every body under its title, or none"), // Creating and editing. info("add-below", C::AddBelow, "new sibling below"), info("add-above", C::AddAbove, "new sibling above"), info("add-child", C::AddChild, "new child"), info("edit-title", C::EditTitle, "edit the title"), info("edit-title-start", C::EditTitleAtStart, "edit the title from its start"), info("retype-title", C::RetypeTitle, "retype the title"), info("edit-body", C::EditBody, "edit the body"), info("edit-body-start", C::EditBodyAtStart, "edit the body from its start"), info("delete-node", C::DeleteNode, "delete just this node (children move up)"), operator("delete", Op::Delete, "delete…"), operator("yank", Op::Yank, "yank (copy)…"), operator("change", Op::Change, "change…"), operator("indent", Op::Indent, "indent…"), operator("outdent", Op::Outdent, "outdent…"), info("delete-branch", C::Operate(Op::Delete, Target::Branch), "delete the branch"), info("delete-children", C::Operate(Op::Delete, Target::Children), "delete the children"), info("yank-branch", C::Operate(Op::Yank, Target::Branch), "yank the branch"), info("yank-children", C::Operate(Op::Yank, Target::Children), "yank the children"), info("delete-selection", C::Operate(Op::Delete, Target::Selection), "delete the selection"), info("yank-selection", C::Operate(Op::Yank, Target::Selection), "yank the selection"), info("indent-selection", C::Operate(Op::Indent, Target::Selection), "indent the selection"), info("outdent-selection", C::Operate(Op::Outdent, Target::Selection), "outdent the selection"), info("paste-after", C::PasteAfter, "paste after"), info("paste-before", C::PasteBefore, "paste before"), info("paste-as-child", C::PasteAsChild, "paste as children"), info("paste-as-sibling", C::PasteAsSibling, "paste as siblings below"), 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"), // Arranging. info("tidy-map", C::Tidy(Layout::Map), "tidy: a mind map"), info("tidy-chart", C::Tidy(Layout::Chart), "tidy: an org chart"), info("tidy-grid", C::Tidy(Layout::Grid), "tidy: a grid beside it"), info("align-left", C::Align(Side::Left), "left edges in a line"), info("align-right", C::Align(Side::Right), "right edges in a line"), info("align-top", C::Align(Side::Top), "top edges in a line"), info("align-bottom", C::Align(Side::Bottom), "bottom edges in a line"), info("align-center", C::Align(Side::Center), "middles in a column"), // Looks, tags, portals. info("pick-color", C::PickColor, "color…"), info("pick-style", C::PickStyle, "style…"), info("pick-border", C::PickBorder, "border…"), info("edit-tags", C::EditTags, "tags…"), info("new-portal", C::NewPortal, "new portal…"), info("capture", C::Capture, "capture an idea to the inbox…"), // Modes. info("visual", C::Visual, "select"), info("visual-branches", C::VisualBranches, "select whole branches"), info("move-mode", C::MoveMode, "move mode"), info("connect-mode", C::ConnectMode, "connect mode"), info("command-line", C::CommandLine, "command line"), info("escape", C::Escape, "cancel"), // In visual, move and connect modes. info("swap-ends", C::SwapEnds, "the other end of the selection"), info("far-left", C::FarLeft, "left by five"), info("far-down", C::FarDown, "down by five"), info("far-up", C::FarUp, "up by five"), info("far-right", C::FarRight, "right by five"), info("toggle-whole", C::ToggleWhole, "the whole branch, or just this node"), info("confirm", C::Confirm, "confirm"), info("remove-link", C::RemoveLink, "remove the link"), info("attach-as-child", C::AttachAsChild, "attach as a child here"), info("cycle-arrows", C::CycleArrows, "arrowheads"), info("cycle-line", C::CycleLine, "line kind"), info("edit-label", C::EditLabel, "label…"), // Panes and documents. info("split-vertical", C::SplitVertical, "split side by side"), info("split-horizontal", C::SplitHorizontal, "split above and below"), info("focus-left", C::FocusLeft, "the pane on the left"), info("focus-down", C::FocusDown, "the pane below"), info("focus-up", C::FocusUp, "the pane above"), info("focus-right", C::FocusRight, "the pane on the right"), info("next-pane", C::NextPane, "the next pane"), info("close-pane", C::ClosePane, "close the pane"), info("only-pane", C::OnlyPane, "close the other panes"), info("equalize-panes", C::EqualizePanes, "even out the panes"), info("next-document", C::NextDocument, "the next document"), info("previous-document", C::PreviousDocument, "the previous document"), info("pick-document", C::PickDocument, "documents…"), info("show-canvas", C::ShowCanvas, "canvas"), info("show-outline", C::ShowOutline, "outline"), info("show-note", C::ShowNote, "note"), // Help and the app. info("help", C::Help, "help: every key"), info("toggle-which-key", C::ToggleWhichKey, "key hints on or off"), info("close", C::Close, "close the pane"), ]; /// The command with this name in `keys.toml`. pub fn by_name(name: &str) -> Option<&'static CommandInfo> { COMMANDS.iter().find(|info| info.name == name) } /// What a binding does, in words (for which-key and the help). pub fn help_for(bound: Bound) -> &'static str { COMMANDS.iter().find(|info| info.bound == bound).map_or("", |info| info.help) } #[cfg(test)] mod tests { use super::*; #[test] fn names_are_unique_kebab_case() { for (index, info) in COMMANDS.iter().enumerate() { assert!(info.name.chars().all(|c| c.is_ascii_lowercase() || c == '-'), "{}", info.name); assert!(COMMANDS[index + 1..].iter().all(|other| other.name != info.name), "{} twice", info.name); assert!( COMMANDS[index + 1..].iter().all(|other| other.bound != info.bound), "{:?} twice", info.bound ); } } #[test] fn registers_by_name() { assert_eq!(Register::named('A'), Some(Register::Named('a'))); assert_eq!(Register::named('*'), Some(Register::Clipboard)); assert_eq!(Register::named('1'), None); } }