Skip to content

Vim mode

Vim mode adds Vim’s modal editing to Tyrn. It covers the core of Vim and replaces some Vim features with Tyrn’s own, such as language servers, multiple cursors and project search. It does not aim to match Vim one to one.

Turn it on

Run workspace: toggle vim mode from the command palette. This switches the vim_mode setting:

settings.json
{ "vim_mode": true }

Tyrn also has a Helix mode that builds on Vim mode.

What works differently

  • Motions use the syntax tree. For example, % matches brackets in a language-aware way.
  • Visual block mode is built on multiple cursors, so what you type after a block selection appears on every line.
  • Macros use Tyrn’s recording, so they can include things like accepting a completion.
  • Search (/, ?, :s) uses Tyrn’s search. Set vim.use_regex_search to choose between regex and plain text.

Language server and Tyrn commands

These work in normal mode.

Task Keys
Go to definition g d
Go to declaration g D
Go to type definition g y
Go to implementation g I
Find all references g A
Rename c d
Symbols in this file g s
Symbols in the project g S
Next, previous diagnostic g ], g [ (also ] d, [ d)
Hover g h
Code actions g .
Project search g /
Open the search excerpt under the cursor g space
Open it in a split ctrl-w space
Go to definition in a split ctrl-w g d

Newer-style bindings also work: g r n renames, g r r finds references, g r i goes to implementation and g r a opens code actions.

Git

Task Keys
Next, previous change ] c, [ c
Expand a diff hunk d o
Toggle staged d O
Stage and next d u
Unstage and next d U
Restore the change d p

Syntax tree

Task Keys
Next, previous method start ] m, [ m
Next, previous method end ] M, [ M
Next, previous section start ] ], [ [
Next, previous section end ] [, [ ]
Next, previous comment ] /, [ / (also ] *, [ *)
Select a larger syntax node [ x
Select a smaller syntax node ] x

Text objects, used after an operator with i (inside) or a (around):

Object Keys
Class or type a c, i c
Function or method a f, i f
Comment g c
Argument a a, i a
HTML tag a t, i t
Indent level i i (and a i, a I)
Entire file a e, i e
Quotes q (quote pair near the cursor), plus ', " and `
Brackets b, B, r, (, {, [, <

Multiple cursors

Task Keys
Add a cursor at the next match of the word g l
Add a cursor at the previous match g L
Select every match g a
Skip this match and add the next g >
Skip this match and add the previous g <
Add a cursor at the end of each selected line (visual mode) g A
Add a cursor at the first character of each selected line (visual mode) g I

Plugin-style features

  • Surround: ys adds, cs changes, ds deletes. For example ysiw" surrounds a word with quotes.
  • Comment: g c c toggles the current line, and g c in visual mode toggles the selection. g b toggles block comments.
  • Replace with register: g R.
  • Exchange: c x.
  • Indent-aware motions: [ -, ] -, [ +, ] +, [ =, ] =.
  • Case and rotation: g u, g U, g ~, g ?.
  • Reflow: g q or g w.

Insert mode

Task Keys
Open completions ctrl-x ctrl-o
Request an edit prediction ctrl-x ctrl-c
Open code actions ctrl-x ctrl-l
Hide suggestions ctrl-x ctrl-z
Word completions ctrl-n, ctrl-p
Signature help ctrl-s

Command palette and ex commands

Press : to open the command palette. Vim-style commands work there, and any other Tyrn command does too. Square brackets show the part you may leave out.

Command Action
:w[rite][!], :up[date] Save
:wa[ll][!] Save all
:q[uit][!] Close the buffer
:wq[!], :x[it] Save and close the buffer
:qa[ll][!], :wqa[ll], :xa[ll] Close all, with or without saving
:cq[uit] Quit Tyrn
:bd[elete][!] Close the file in all panes
:sp[lit], :vs[plit] Split the pane horizontally or vertically
:new, :vne[w] New file in a split
:tabe[dit], :tabnew New file in a tab
:tabn[ext], :tabp[revious], :tabc[lose], :tabo[nly] Move between and close tabs
:ls, :buffers Show open buffers
:E[xplore] Open the project panel
:G[it] Open the git panel
:D[ebug] Open the debug panel
:te[rm] Open the terminal panel
:cl[ist] Open project diagnostics
:cn[ext], :cp[rev] Next, previous diagnostic
:dif[fupdate] Expand the diff hunk (d o)
:rev[ert] Revert the change (d p)
:<number>, :$ Jump to a line, or to the end
:[range]s/foo/bar/[g] Substitute
:j[oin], :d[elete], :y[ank], :sor[t] Edit lines
:norm[al] Run normal-mode keys
:noh[lsearch] Clear search highlights
:se[t] [no]wrap, [no]nu[mber], [no]r[elative]nu[mber], [no]i[gnore]c[ase] Buffer options

Add your own short names with command_aliases.

Settings

settings.json
{
"vim": {
"default_mode": "normal",
"use_system_clipboard": "always",
"use_smartcase_find": false,
"use_regex_search": true,
"gdefault": false,
"toggle_relative_line_numbers": false,
"highlight_on_yank_duration": 200,
"custom_digraphs": {},
"cursor_shape": {
"normal": "block",
"replace": "underline",
"visual": "block",
"insert": "inherit"
}
}
}
Key Meaning
default_mode Mode to start in: "normal", "insert", "replace", "visual", "visual_line", "visual_block" or "helix_normal"
use_system_clipboard "always" (default), "never" or "on_yank"
use_smartcase_find Make f and t ignore case for a lowercase target
use_regex_search Treat / searches as regular expressions
gdefault Make :s replace every match in a line by default
toggle_relative_line_numbers Relative numbers in normal mode, absolute in insert mode
highlight_on_yank_duration Milliseconds to flash yanked text. 0 turns it off.
custom_digraphs Extra digraphs, typed with ctrl-k then two characters
cursor_shape Cursor per mode. "inherit" uses the editor’s cursor_shape.

Other settings that pair well with Vim mode: relative_line_numbers, cursor_blink, scroll_beyond_last_line, vertical_scroll_margin and command_aliases.

Custom key bindings

Vim mode adds attributes to the Editor context. Use them in key bindings.

Context Meaning
VimControl Vim keys are active (normal, visual or operator mode)
vim_mode == normal Normal mode
vim_mode == visual Visual mode
vim_mode == insert Insert mode
vim_mode == replace Replace mode
vim_mode == waiting Waiting for a key, for example after f
vim_mode == operator Waiting for a motion after an operator such as d
vim_operator The pending operator, such as d. none otherwise.

Contexts match one node at a time. Editor && vim_mode == normal works. Workspace && vim_mode == normal never matches.

keymap.json
[
{
"context": "vim_mode == insert",
"bindings": {
"j k": "vim::NormalBefore"
}
},
{
"context": "vim_mode == normal && !menu",
"bindings": {
"shift-y": ["workspace::SendKeystrokes", "y $"]
}
}
]

To emulate Vim’s map commands, use workspace::SendKeystrokes in the right context.

Optional bindings

Move between panels with the same keys as between panes:

keymap.json
{
"context": "Dock",
"bindings": {
"ctrl-w h": "workspace::ActivatePaneLeft",
"ctrl-w l": "workspace::ActivatePaneRight",
"ctrl-w k": "workspace::ActivatePaneUp",
"ctrl-w j": "workspace::ActivatePaneDown"
}
}

Subword motion for camelCase and snake_case is off by default:

keymap.json
{
"context": "VimControl && !menu && vim_mode != operator",
"bindings": {
"w": "vim::NextSubwordStart",
"b": "vim::PreviousSubwordStart",
"e": "vim::NextSubwordEnd",
"g e": "vim::PreviousSubwordEnd"
}
}

Add surround in visual mode, enable the Sneak motion, or make h and l wrap across lines:

keymap.json
[
{ "context": "vim_mode == visual", "bindings": { "shift-s": "vim::PushAddSurrounds" } },
{
"context": "vim_mode == normal || vim_mode == visual",
"bindings": { "s": "vim::PushSneak", "shift-s": "vim::PushSneakBackward" }
},
{
"context": "VimControl && !menu",
"bindings": { "h": "vim::WrappingLeft", "l": "vim::WrappingRight" }
}
]

Project panel and other panels

With the project panel focused: j and k move, h and l collapse and expand, o opens in a horizontal split, v in a vertical split, t opens permanently, % creates a file, d creates a folder, shift-r renames, shift-d deletes and / searches inside the folder. escape returns to the editor. The outline panel and git panel accept j, k, g g and shift-g too.