diff --git a/DESIGN.md b/DESIGN.md index f0e387d..263acbd 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -157,9 +157,11 @@ lines. - A register holds text or nodes, and either pastes anywhere: nodes pasted into a text give their titles, text pasted on nodes becomes nodes. `yy`, `yab`, `yib` yank nodes; every delete (`dd`, `x`, `dib`, `d` on a - selection) keeps what it took, so `dd` then `p` moves a branch. `p` `P` - `]p` paste after, before, or as the last children, keeping the yanked - layout, where a new node would go; a count pastes copies. Links between + selection) keeps what it took, so `dd` then `p` moves a branch. `p` + pastes as the node's last children and `P` as its next siblings (the + user's pick after trying part 8c: `p` goes under the node as `a` adds, + `P` below it as `o` adds), keeping the yanked layout, where a new node + would go; a count pastes copies. Links between the pasted nodes come along, and so do links to nodes still there when they go back into the document they came from. - `"+` is the system clipboard. Nodes go there as Markdown, the user's @@ -421,7 +423,7 @@ text). | **Move** (every view) | `hjkl` or arrows · `gh gj gk gl` tree moves · `w` `b` next / previous in reading order · `gg` root · `G` last · `/` `n` `N` search · `Ctrl-o` `Ctrl-i` (Tab) jump back / forward · `m{a-z}` `'{a-z}` marks (`A–Z` across documents) · Enter or `gf` through a portal · `gd` follow a cross-link | | **View** | `zz` center · `Ctrl-e` `Ctrl-y` `zh` `zl` pan · `Ctrl-d` `Ctrl-u` half a screen · `+` `-` zoom · `za zo zc zR zM` folds · `zf` Auto · `zi` focus · `K` body card | | **Create and edit** | `o` `O` sibling below / above · `a` child · `i` `A` Enter edit the title · `I` edit from its start · `cc` `S` retype | -| **Operators** | `dd` or `dab` delete branch · `dib` delete children · `x` delete just the node · `yy` `yab` `yib` yank · `p` `P` paste after / before · `]p` paste as child · `>>` `<<` indent / outdent · `"a`–`"z` and `"+` registers · `.` repeat · `u` `Ctrl-r` undo / redo | +| **Operators** | `dd` or `dab` delete branch · `dib` delete children · `x` delete just the node · `yy` `yab` `yib` yank · `p` paste as children · `P` paste as siblings below · `>>` `<<` indent / outdent · `"a`–`"z` and `"+` registers · `.` repeat · `u` `Ctrl-r` undo / redo | | **Modes** | `v` `V` visual · `M` move · `C` connect · `:` command · Esc back to normal | | **Leader** `` | `c` color · `s` style · `B` border · `t` tags · `p` new portal · `i` inbox · `b` documents · `v` then `c`/`o`/`n` for canvas / outline / note · `w` which-key on/off · `?` help | | **Panes, documents** | `Ctrl-w v` `Ctrl-w s` split · `Ctrl-w hjkl` focus · `Ctrl-w w` the next pane · `Ctrl-w c` close · `Ctrl-w o` only · `Ctrl-w =` even sizes · `gt` `gT` `]b` `[b` next / previous document · `ZZ` close pane (the last one quits) | diff --git a/README.md b/README.md index ac94c65..a45b779 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ The plan for the everyday keys (all of them are in DESIGN.md): | `o` `O` `a` | new sibling below / above, new child: type its title, Enter for the next one | | `i` or Enter | edit the title (Esc: vim keys inside it, Esc again: done) | | `dd` `x` | delete the branch / just the node (kept in a register, as in vim) | -| `yy` `p` `P` `]p` | yank the branch / paste after, before, as a child (`dd` then `p` moves a branch) | +| `yy` `p` `P` | yank the branch / paste as children, or as siblings below (`dd` then `p` moves a branch) | | `"a` `"+` | before `y`, `d` or `p`: register `a`; `"+` is the system clipboard (nodes go there as Markdown) | | `.` | the last change again | | `>>` `<<` | under the node above / out a level | diff --git a/src/app/action.rs b/src/app/action.rs index 03c19ab..45ae6ac 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -145,7 +145,7 @@ pub enum AppAction { nodes: Vec, branches: bool, }, - /// Paste what a register holds (`p`, `P`, `]p`): its nodes, or its text + /// Paste what a register holds (`p`, `P`): its nodes, or its text /// made into nodes, `times` copies. Paste { register: Option, @@ -256,15 +256,14 @@ pub enum AppAction { Say(Message), } -/// Where `p` and its kin paste, from the cursor's node. +/// Where `p` and `P` paste, from the cursor's node (the user's pick after +/// trying part 8c: `p` pastes under the node, as `a` adds). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PasteAt { - /// Its next siblings (`p`). - After, - /// Its siblings before it (`P`). - Before, - /// Its last children (`]p`). + /// Its last children (`p`). Child, + /// Its next siblings, below it, where `o` adds one (`P`). + Sibling, } /// What `:w` and its kin save. diff --git a/src/app/commands.rs b/src/app/commands.rs index 3921dcd..88c2f82 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -64,13 +64,12 @@ pub fn shared(typed: &Typed, ctx: &ViewContext<'_>) -> Option> { app(AppAction::Yank { register: typed.register, nodes: children.to_vec(), branches: true }) } }, - Command::PasteAfter => app(AppAction::Paste { register: typed.register, at: PasteAt::After, times }), - Command::PasteBefore => { - app(AppAction::Paste { register: typed.register, at: PasteAt::Before, times }) - } Command::PasteAsChild => { app(AppAction::Paste { register: typed.register, at: PasteAt::Child, times }) } + Command::PasteAsSibling => { + app(AppAction::Paste { register: typed.register, at: PasteAt::Sibling, times }) + } Command::Operate(Operator::Indent, Target::Current) => indent(ctx, &siblings(ctx, times), None), Command::Operate(Operator::Outdent, Target::Current) => outdent(ctx, &siblings(ctx, times), None), Command::ToggleFold => toggle_fold(ctx), @@ -415,17 +414,16 @@ fn delete_children(ctx: &ViewContext<'_>, register: Option) -> Vec, clip: Clip, at: PasteAt, times: u32) -> Option { let (doc, cursor) = (ctx.doc, ctx.cursor); let bounds = clip.bounds(|node| ctx.box_size(node))?; let rects = ctx.box_rects(); let (spot, start) = match at { - PasteAt::After => (Spot::After(cursor), below_spot(cursor, bounds.size, &rects)), - PasteAt::Before => (Spot::Before(cursor), below_spot(cursor, bounds.size, &rects)), PasteAt::Child => (Spot::LastChildOf(cursor), child_spot(doc, cursor, bounds.size, &rects)), + PasteAt::Sibling => (Spot::After(cursor), below_spot(cursor, bounds.size, &rects)), }; let mut offsets = Vec::new(); let mut pos = start; diff --git a/src/app/popup.rs b/src/app/popup.rs index 0080e4d..d4fbb0d 100644 --- a/src/app/popup.rs +++ b/src/app/popup.rs @@ -494,8 +494,8 @@ impl Popup { Popup::Menu(Runs { list, runs }) } - /// `:registers`: what each register holds. Enter pastes it after the - /// cursor's node. + /// `:registers`: what each register holds. Enter pastes it under the + /// cursor's node, as `p` does. pub fn registers(registers: &[(char, String)], theme: &Theme) -> Popup { let key = Style::new().fg(theme.accent).add_modifier(Modifier::BOLD); let muted = Style::new().fg(theme.muted); @@ -507,7 +507,7 @@ impl Popup { rows.push(ListRow::new(spans, what)); runs.push(Run::Keys(vec![Key::char('"'), Key::char(name), Key::char('p')])); } - let footer = "Enter: paste it after the cursor's node · Esc: close"; + let footer = "Enter: paste it under the cursor's node · Esc: close"; let list = ListPopup::new("registers", footer, rows, Place::Center { width: CONFLICT_WIDTH }); Popup::Menu(Runs { list, runs }) } diff --git a/src/app/tests/vim.rs b/src/app/tests/vim.rs index ab4af1b..82ac069 100644 --- a/src/app/tests/vim.rs +++ b/src/app/tests/vim.rs @@ -17,18 +17,18 @@ fn yank_and_paste_nodes() { // yy on Raised beds: the node and its branch. h.type_keys("glgjyy"); assert_eq!(h.message(), "yanked 3 nodes"); - // p on Watering: the copy goes after it, with its children. - h.type_keys("gjp"); + // P on Watering: the copy goes after it, with its children. + h.type_keys("gjP"); assert_eq!(top(&h), ["Soil", "Raised beds", "Watering", "Raised beds", "Kitchen"]); let copy = h.doc().children(h.node("Garden plans"))[3]; assert_eq!(h.app.current().cursor(), copy, "the cursor goes to what was pasted"); let titles: Vec = h.doc().children(copy).iter().map(|&id| h.title(id)).collect(); assert_eq!(titles, ["Cedar, untreated", "40 cm high"]); assert_eq!(h.doc().node(copy).unwrap().look.color.as_ref().map(|c| c.as_str()), Some("orange")); - // ]p: as the last children; 2p: two copies. - h.type_keys("gg]p"); + // p: as the last children; 2P: two copies. + h.type_keys("ggp"); assert_eq!(top(&h).len(), 6); - h.type_keys("u2p"); + h.type_keys("u2P"); assert_eq!(h.message(), "pasted 6 nodes"); assert_eq!(top(&h), ["Soil", "Raised beds", "Watering", "Raised beds", "Kitchen"]); assert_eq!(h.doc().loose().len(), 4, "after the root: loose nodes, before the two there were"); @@ -39,32 +39,34 @@ fn yank_and_paste_nodes() { #[test] fn deletes_keep_what_they_take() { let mut h = Harness::new(); - // dd then p: the branch moves. + // dd then P: the branch moves, after Watering. h.type_keys("glgjdd"); assert_eq!(h.cursor(), "Watering"); - h.type_keys("p"); + h.type_keys("P"); assert_eq!(top(&h), ["Soil", "Watering", "Raised beds", "Kitchen"]); assert_eq!(h.children("Raised beds"), ["Cedar, untreated", "40 cm high"]); // x keeps just the node: its children stay behind. h.type_keys("x"); assert_eq!(top(&h), ["Soil", "Watering", "Cedar, untreated", "40 cm high", "Kitchen"]); - h.type_keys("ggP"); - assert_eq!(h.message(), "nothing goes before the root"); - h.type_keys("glP"); - assert_eq!(top(&h)[0], "Raised beds", "P: before Soil"); + // p: under the node, as its last child. + h.type_keys("ggglp"); + assert_eq!(h.children("Soil"), ["Raised beds"]); assert!(h.children("Raised beds").is_empty()); + // P on the root: a loose node, where o adds one. + h.type_keys("ggP"); + assert_eq!((h.cursor().as_str(), h.doc().loose().len()), ("Raised beds", 3)); // Named registers, and the black hole. - h.type_keys("gl\"ayy"); + h.type_keys("\"ayy"); assert_eq!(h.message(), "yanked 1 node into \"a"); - h.type_keys("gj\"_dd"); + h.type_keys("gggl\"_x"); assert_eq!(h.message(), "deleted 1 node · u to undo"); - h.type_keys("gg]p"); + h.type_keys("ggp"); assert_eq!( h.children("Garden plans").last().map(String::as_str), Some("Raised beds"), "\"_ kept nothing" ); - h.type_keys("gg\"a]p"); + h.type_keys("gg\"ap"); assert_eq!(h.children("Garden plans").iter().filter(|title| *title == "Raised beds").count(), 3); } @@ -81,7 +83,7 @@ fn the_clipboard_holds_markdown() { assert_eq!(h.doc().node(pasted).unwrap().look.color.as_ref().map(|c| c.as_str()), Some("orange")); // Text another app put there: a node per section, tags and body too. h.app.registers.clipboard().write("## Mulch\n---\n- todo\n---\nStraw or leaves.\n\n### Straw\n").unwrap(); - h.type_keys("gg\"+]p"); + h.type_keys("gg\"+p"); assert_eq!(h.cursor(), "Mulch"); assert_eq!((h.tags("Mulch").as_str(), h.body("Mulch").as_str()), ("#todo", "Straw or leaves.")); assert_eq!(h.children("Mulch"), ["Straw"]); @@ -97,9 +99,9 @@ fn text_and_nodes_paste_into_each_other() { // A word yanked in a title, pasted on nodes: a node. h.type_keys("gliyiwggp"); assert_eq!(h.cursor(), "Soil"); - assert_eq!(h.doc().loose().len(), 3, "after the root: a loose node"); + assert_eq!(top(&h).last().map(String::as_str), Some("Soil"), "under the root, at the end"); // Nodes pasted into a title: their titles. - h.type_keys("gg]pu"); + h.type_keys("ggpu"); h.type_keys("glgjyyggi p"); assert_eq!(h.draft(), "Garden plans Raised beds Cedar, untreated 40 cm high"); } @@ -141,10 +143,10 @@ fn visual_mode_takes_in_a_rectangle() { h.type_keys("d"); assert_eq!(top(&h), ["Kitchen"]); assert_eq!(h.message(), "deleted 8 nodes · u to undo"); - // What went is in the register: ]p puts it back under the root, where + // What went is in the register: p puts it back under the root, where // the cursor went. assert_eq!(h.cursor(), "Garden plans"); - h.type_keys("]p"); + h.type_keys("p"); assert_eq!(top(&h), ["Kitchen", "Soil", "Raised beds", "Watering"]); } @@ -393,9 +395,9 @@ fn registers_list_and_paste() { let screen = h.screen(); assert!(screen.contains("\"a 1 node") && screen.contains("\"b 3 nodes"), "{screen}"); assert!(screen.contains("\"+ the system clipboard")); - // Enter on "a: pasted after the cursor's node. + // Enter on "a: pasted under the cursor's node. h.type_keys("j"); - assert_eq!(top(&h), ["Soil", "Raised beds", "Soil", "Watering", "Kitchen"]); + assert_eq!(h.children("Raised beds"), ["Cedar, untreated", "40 cm high", "Soil"]); } #[test] diff --git a/src/app/yank.rs b/src/app/yank.rs index cff6629..14015dc 100644 --- a/src/app/yank.rs +++ b/src/app/yank.rs @@ -1,5 +1,5 @@ //! Nodes into registers and back: `yy` and the deletes keep nodes in a -//! register, `p` `P` `]p` paste them (or a register's text, made into +//! register, `p` and `P` paste them (or a register's text, made into //! nodes). See [`registers`](super::registers). use std::collections::BTreeSet; diff --git a/src/input/bindings.rs b/src/input/bindings.rs index ccb38e9..37d674a 100644 --- a/src/input/bindings.rs +++ b/src/input/bindings.rs @@ -208,9 +208,8 @@ const NORMAL: &[DefaultBinding] = &[ operator("y", Op::Yank), operator(">", Op::Indent), operator("", Op::Outdent), - bind("p", C::PasteAfter), - bind("P", C::PasteBefore), - bind("]p", C::PasteAsChild), + bind("p", C::PasteAsChild), + bind("P", C::PasteAsSibling), bind("u", C::Undo), bind("", C::Redo), bind(".", C::Repeat), diff --git a/src/input/command.rs b/src/input/command.rs index 8efd801..aa9dac0 100644 --- a/src/input/command.rs +++ b/src/input/command.rs @@ -254,9 +254,14 @@ pub enum Command { 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, @@ -468,7 +473,8 @@ pub const COMMANDS: &[CommandInfo] = &[ 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 a child"), + 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"), diff --git a/src/input/engine.rs b/src/input/engine.rs index 483317d..2ad431f 100644 --- a/src/input/engine.rs +++ b/src/input/engine.rs @@ -389,11 +389,11 @@ mod tests { assert_eq!(type_keys(&mut engine, &keymap, "12j"), run(Command::Move(Motion::Down), Some(12), None)); assert_eq!( type_keys(&mut engine, &keymap, "\"+p"), - run(Command::PasteAfter, None, Some(Register::Clipboard)) + run(Command::PasteAsChild, None, Some(Register::Clipboard)) ); assert_eq!( type_keys(&mut engine, &keymap, "3\"ap"), - run(Command::PasteAfter, Some(3), Some(Register::Named('a'))) + run(Command::PasteAsChild, Some(3), Some(Register::Named('a'))) ); assert_eq!(type_keys(&mut engine, &keymap, "gq"), Outcome::Unknown("gq".into())); assert_eq!(type_keys(&mut engine, &keymap, "z"), Outcome::Cancelled);