The first small part of stage 9 (v1.1). <Space>= then m, c or g (the user's pick: leader menus), or :tidy map|chart|grid, lays out the cursor's branch while its top node stays put: a mind map puts each node in the middle of its children (the user's pick), an org chart puts them side by side below, and a grid makes a block of cards beside the node (the user's pick), a card's own branch going along as a mind map. Every node of the branch gets a place, folded away or not (the user's pick), as one undo step, and the message says when the branch now covers nodes outside it. The lines follow the layouts: a parent whose children are all below it sends its branches down to a shared bend, and a new child of it goes beside the last one; a grid card behind another isn't joined by a line, which would look like it joins the two cards. In visual mode, <Space>a then h, l, k, j or c lines the selection up on the box around it, each node bringing its branch along, and : opens the command line on the selection: :align and :tidy take it, and so do :color, :border, :style, :tag and :fold, as one undo step. The layouts are pure functions in view::canvas::arrange, applied as one EditOp::Reposition. Layout and Side moved from the parser to the model so keys can name them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
601 lines
22 KiB
Rust
601 lines
22 KiB
Rust
//! 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<Target> {
|
||
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<Register> {
|
||
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);
|
||
}
|
||
}
|