Vim mode
Vim keybindings for editing in Pasty, off by default. Turn them on from ⌘K → Enable Vim mode, or from Settings (in the account menu, and in the ⌘K palette). It applies everywhere, not per document, and it is remembered: on your account if you have one, in this browser if you do not, and it survives signing up.
What works
Motions (h j k l, w b e, W B E, ge gE, 0 ^ $ g_, gg G, { }, counts), insertion (i I a A o O gi), the single-key edits (x X r s J u <C-r>), the operators d c y > < with dd cc yy >> <<, p P, v V gv, text objects (iw aw iW aW, i" a", i( a(, i[ i{), marks (m{a-z}, `{a}, '{a}, and two backticks to return to the previous jump), the full register set ("a–"z, "A–"Z to append, "0, "1–"9, "_, "+), repeat (.), search (/ ? n N * #), character find (f F t T ; ,), matching brackets (%), the shorthands (D C Y ~), macros (q{a-z}, @{a-z}, @@), and the scroll family (<C-e> <C-y> <C-d> <C-u> <C-f> <C-b>).
The supported set is defined by a test, not a list: a command is not supported until it has a case in the compatibility suite, and every place Pasty deliberately does something other than terminal Vim is recorded there too.
Three things worth knowing
jandkmove by logical line, not by rendered row, so a wrapped paragraph is one line. The gutter down the left shows those same logical lines, so what it says and what42Gdoes are the same thing. The engine measures no geometry at all, which is what makes every motion deterministic and testable without a browser.gj/gkand Visual Block are not supported for the same reason.Search patterns are Vim patterns, with smartcase: a bounded subset of magic mode rather than the whole language.
.*[…]^$, the backslashed forms (\+ \? \| \( \) \{n,m}), the word boundaries\<\>, the classes\w \s \d \aand their negations, and\c/\C. Anything else (\zs,\%(, lookaround, a pattern spanning lines) is refused withInvalid patternrather than reinterpreted, and JavaScript's own regex syntax never leaks through: in magic mode+ ? ( ) { } |are ordinary characters, so searching forf(x)findsf(x).>>means nesting, in a list, and two literal spaces inside a code block. On a paragraph it does nothing: Markdown has no indented paragraph, and four leading spaces are a code block.
A macro is a list of commands, not keystrokes, so replaying re-resolves every motion where the cursor now is. A macro stops when a command in it fails (j on the last line, 5rZ on a two-character line), which is what makes 9@a terminate at the end of a document; a command that succeeded without moving anything, like 0 in column 1, does not stop it. Clipboard puts are deliberately not recorded: "+p completes asynchronously after a permission prompt, and a macro containing one would sometimes run out of order.
The command line
/, ? and : open a command line at the bottom of the page. : accepts eight spellings: :s/pat/repl/, :s/pat/repl/g, the same two with a % range, :noh/:nohlsearch, and :w/:w!. :w is not a second way to save: Pasty saves continuously, so it flushes what is already in flight and says Saved only once it has actually landed. Everything else it refuses out loud. :q in a browser tab and :e with no filesystem each have an obvious wrong answer, and a command line that half-works is worse than one that says what it does not do.
Who owns a keystroke
Application shortcuts always win: ⌘K, ⌘Z, ⌘V and every other modified chord reach their owners untouched, and an input method is never interrupted. Ctrl is the other way round, and deliberately so: in NORMAL, VISUAL and VISUAL LINE every Ctrl chord belongs to Vim, whether implemented, aliased (<C-[> and <C-c> are Escape), answered (<C-v> says Visual Block is not supported), or swallowed. Left unclaimed they would reach macOS's emacs bindings inside the document, where Ctrl-D deletes and Ctrl-K kills the line while the indicator still says NORMAL. INSERT is the opposite: it is ordinary typing, so the platform keeps its bindings there.
Debugging
Debugging a keyboard problem is otherwise guesswork, so there is a logger, and it ships off. localStorage.setItem('pasty.vim_debug', '1') prints every keydown with the engine's verdict for it (claimed: motion, consumed: unsupported-modal-chord, passed: modifier-passthrough, …), and window.__pasty.vim.debugMacro('a') prints what a macro actually recorded. Removing the key silences it immediately; nothing is logged or sent anywhere without it.