pi-vim
v0.1.8
Published
Vim-style modal editing for Pi's TUI editor
Downloads
711
Maintainers
Readme
vim-bindings — Pi REPL Vim Mode
A modal vim-like editing extension for Pi's REPL prompt, covering the high-frequency ("90%") command surface without trying to clone full Vim.
Loading
pi --extension /path/to/vim-bindings/index.tsOr add to .pi/settings.json:
{
"extensions": ["./pi-extensions/vim-bindings/index.ts"]
}The mode indicator (INSERT / NORMAL) is shown in the bottom-right corner
of the prompt.
Supported command surface
Mode switching
| Key | Action |
|----------|----------------------------------------|
| Esc | Insert → Normal mode |
| Esc | Normal mode → pass to Pi (abort agent) |
| i | Normal → Insert at cursor |
| a | Normal → Insert after cursor |
| I | Normal → Insert at line start |
| A | Normal → Insert at line end |
| o | Normal → open line below + Insert |
| O | Normal → open line above + Insert |
Insert-mode shortcuts (stay in Insert mode):
| Key | Action |
|-----------------|------------------------|
| Shift+Alt+A | Go to end of line |
| Shift+Alt+I | Go to start of line |
| Alt+o | Open line below |
| Alt+Shift+O | Open line above |
Navigation (Normal mode)
A {count} prefix can be prepended to any navigation key (max: 9999).
| Key | Action |
|---------------|-------------------------------|
| h | Left |
| l | Right |
| j | Down |
| k | Up |
| {count}h/l | Move left/right {count} cols |
| {count}j/k | Move down/up {count} lines (clamped to buffer size) |
| 0 | Line start |
| $ | Line end |
| gg | Buffer start (line 1) |
| {count}gg | Go to line {count} (1-indexed, clamped) |
| G | Buffer end (last line) |
| {count}G | Go to line {count} (1-indexed, clamped) |
| w | Next word start (keyword/punctuation aware) |
| b | Previous word start |
| e | word end (inclusive) |
| W | Next WORD start (whitespace-delimited token) |
| B | Previous WORD start |
| E | WORD end (inclusive) |
| {count}w/b/e| Move {count} word motions |
| {count}W/B/E| Move {count} WORD motions |
| } | Move to next paragraph start (line start col 0) |
| { | Move to previous paragraph start (line start col 0) |
| {count}} | Repeat } {count} times |
| {count}{ | Repeat { {count} times |
word (w/b/e) splits punctuation from keyword chars. WORD (W/B/E)
treats any non-whitespace run as one token (foo-bar, path/to, x.y).
Paragraph boundary definition (this extension wave):
- blank line: matches
^\s*$ - paragraph start: non-blank line at BOF, or non-blank line immediately after a blank line
Standalone { / } motions are navigation-only (no text/register mutation).
Counted forms ({count}{, {count}}) step paragraph-by-paragraph.
If no further paragraph boundary exists, motions clamp at BOF/EOF.
Operator forms with braces (d{, d}, c{, c}, y{, y}) are out of scope for this wave.
Character-find motions (Normal mode)
A {count} prefix finds the Nth occurrence of {char} on the line.
| Key | Action |
|------------------|------------------------------------------------|
| f{char} | Jump forward to char (inclusive) |
| F{char} | Jump backward to char (inclusive) |
| t{char} | Jump forward to one before char (exclusive) |
| T{char} | Jump backward to one after char (exclusive) |
| {count}f{char} | Jump to Nth occurrence of char forward |
| ; | Repeat last f/F/t/T motion |
| , | Repeat last motion in reverse direction |
Char-find motions compose with operators: df{char}, ct{char}, d{count}t{char}, etc.
Edit operators (Normal mode)
All operators write to the unnamed register and mirror to the system clipboard (best-effort; clipboard failure never breaks editing).
Delete d{motion} / dd
A {count} or dual-count prefix ({pfx}d{op}{motion}) is supported for
word, char-find, and linewise motions. Maximum total count: 9999.
| Command | Deletes |
|-------------------|-----------------------------------------------------------|
| dw | Forward to next word start (exclusive, can cross lines) |
| de | Forward to word end (inclusive, can cross lines) |
| db | Backward to word start (exclusive, can cross lines) |
| dW | Forward to next WORD start (exclusive, can cross lines) |
| dE | Forward to WORD end (inclusive, can cross lines) |
| dB | Backward to WORD start (exclusive, can cross lines) |
| d{count}w/e/b | Forward/backward {count} word motions |
| d{count}W/E/B | Forward/backward {count} WORD motions |
| d$ | To end of line |
| d0 | To start of line |
| dd | Current line (linewise) |
| {count}dd | {count} lines (linewise) |
| d{count}j | Current line + {count} lines below (linewise) |
| d{count}k | Current line + {count} lines above (linewise) |
| dG | Current line to end of buffer (linewise) |
| df{char} | To and including char |
| d{count}f{char} | To and including Nth char |
| dt{char} | Up to (not including) char |
| dF{char} | Backward to and including char |
| dT{char} | Backward to one after char |
| diw | Inner word |
| daw | Around word (includes surrounding spaces) |
| d{count}aw | Around {count} words |
Change c{motion} / cc
Same motion and count set as d. Deletes text then enters Insert mode.
| Command | Action |
|-----------------|------------------------------------|
| cw | Change word + Insert |
| ce / cb | Change to word end / previous word start |
| cW | Change WORD + Insert (cW on non-space behaves like cE) |
| cE / cB | Change to WORD end / previous WORD start |
| c{count}w/e/b | Change {count} word motions + Insert |
| c{count}W/E/B | Change {count} WORD motions + Insert |
| ciw | Change inner word |
| caw | Change around word |
| cc | Delete line content + Insert |
| c$ | Delete to EOL + Insert |
| … | All d motions apply |
Single-key edits
A {count} prefix is supported for x, p, P. Maximum: 9999.
| Key | Action |
|--------------|---------------------------------------------------------------|
| x | Delete char under cursor (no-op at/past EOL) |
| {count}x | Delete {count} chars |
| s | Delete char under cursor + Insert mode |
| S | Delete line content + Insert mode |
| D | Delete cursor to EOL (captures \n if at EOL with next line) |
| C | Delete cursor to EOL + Insert mode |
Yank y{motion} / yy
Same motion set as d. Writes to register, no text mutation.
| Command | Yanks |
|---------|---------------------------------|
| yy | Whole line + trailing \n |
| {count}yy | {count} whole lines + trailing \n |
| y{count}j | Current line + {count} lines below (linewise) |
| y{count}k | Current line + {count} lines above (linewise) |
| yG | Current line to end of buffer (linewise) |
| yw | Forward to next word start |
| ye | To word end (inclusive) |
| yb | Backward to word start |
| yW | Forward to next WORD start |
| yE | To WORD end (inclusive) |
| yB | Backward to WORD start |
| y$ | To end of line |
| y0 | To start of line |
| yf{c} | To and including char |
| yiw | Inner word |
| yaw | Around word (includes spaces) |
Counted yank caveat: counted word/WORD yank motions are intentionally not
implemented (y2w, 2yw, y2W, 2yW, etc. cancel the pending operator).
Linewise counted yank ({count}yy, y{count}j/k) remains supported.
Put / Paste
| Key | Action |
|--------------|-------------------------------------------------------------|
| p | Put after cursor (char-wise) / new line below (line-wise) |
| P | Put before cursor (char-wise) / new line above (line-wise) |
| {count}p | Put {count} times after cursor |
| {count}P | Put {count} times before cursor |
Put reads from the unnamed register (not OS clipboard).
Line-wise detection: register content ending in \n is treated as line-wise.
Undo
| Key | Action |
|-----|-------------------------------------------------|
| u | Undo — sends ctrl+_ (\x1f) to the underlying readline editor |
Redo (<C-r>) is not implemented — see Out of scope.
Register and clipboard policy
- One unnamed register (like Vim's
""register). - Every
d,c,x,s,S,D,C,yoperator form (includingdd,{count}dd,d{count}j/k,dG,yy,{count}yy,y{count}j/k,yG) writes to the register and mirrors to the OS clipboard (viacopyToClipboard, best-effort). p/Pread from the unnamed register only (not the OS clipboard).- This gives stable behaviour across local terminals and SSH / OSC52 setups.
Known differences from full Vim
| Area | This extension | Full Vim |
|-----------------------|----------------------------------------|-------------------------------|
| $ motion | Moves past last char (readline CTRL+E) | Moves to last char |
| w / e / b + W / E / B | Cross-line for word + WORD motions | Cross-line |
| 0 / $ operators | Exclusive of anchor col | 0 inclusive of col 0 |
| Undo depth | Delegates to underlying readline undo | Full per-change undo tree |
| Redo | Not implemented | <C-r> |
| Visual mode | Not implemented | v, V, <C-v> |
| Text objects | Supports iw/aw only | Full text-object set |
| Count prefix | Supported for operators, word/char motions, navigation, and edits (x, p/P); capped at MAX_COUNT=9999 to prevent abuse | Full support |
| Named registers | Not implemented ("a, etc.) | Supported |
| Macros | Not implemented (q, @) | Supported |
| Search | Not implemented (/, ?, n, N) | Supported |
| Ex commands | Not implemented (:s, :g, etc.) | Supported |
| Multi-line operators | Supports d/c/y with w/e/b and W/E/B, plus j/k counts and G; not full Vim motion matrix | Rich cross-line semantics |
Out of scope
These are explicitly deferred and not planned for this feature:
- Visual modes (
v,V, block visual) - Extended text objects beyond word (
ip,i",i(, etc.) - Named registers (
"a,"b, …) - Macros (
q{char},@{char}) - Ex command surface (
:s,:g,:r, …) - Search mode (
/,?,n,N) - Repeat (
.) - Extended count prefix beyond currently supported motions (e.g.
:, global operator counts) - Redo (
<C-r>) — no native redo primitive in the underlying readline editor; deferred until a suitable hook is available. - Window / tab / buffer management
- Plugin / runtime ecosystem compatibility
Architecture notes
index.ts—ModalEditorsubclass ofCustomEditor; all key handling.motions.ts— pure motion calculation helpers (findWordMotionTarget,findCharMotionTarget); no side effects.types.ts— shared types and escape-sequence constants.test/— Node test runner suite; no browser / full runtime required.
Run tests:
cd vim-bindings
npm test