Docs for bleeding edge master can be found at https://docs.helix-editor.com/master.

See the usage section for a quick overview of the editor, keymap section for all available keybindings and the configuration section for defining custom keybindings, setting themes, etc. For everything else (e.g., how to install supported language servers), see the Helix Wiki.

Refer the FAQ for common questions.


We provide pre-built binaries on the GitHub Releases page.

Packaging status


Helix is available in homebrew-core:

brew install helix



A flake containing the package is available in the project root. The flake can also be used to spin up a reproducible development shell for working on Helix with nix develop.

Flake outputs are cached for each push to master using Cachix. The flake is configured to automatically make use of this cache assuming the user accepts the new settings on first use.

If you are using a version of Nix without flakes enabled you can install Cachix cli; cachix use helix will configure Nix to use cached outputs when possible.

Arch Linux

Releases are available in the community repository.

A helix-git package is also available on the AUR, which builds the master branch.

Fedora Linux

You can install the COPR package for Helix via

sudo dnf copr enable varlad/helix
sudo dnf install helix

Void Linux

sudo xbps-install helix

Build from source

git clone https://github.com/helix-editor/helix
cd helix
cargo install --path helix-term

This will install the hx binary to $HOME/.cargo/bin.

Helix also needs it's runtime files so make sure to copy/symlink the runtime/ directory into the config directory (for example ~/.config/helix/runtime on Linux/macOS). This location can be overridden via the HELIX_RUNTIME environment variable.

windows(cmd.exe)xcopy /e /i runtime %AppData%/helix/runtime
windows(powershell)xcopy /e /i runtime $Env:AppData\helix\runtime
linux/macosln -s $PWD/runtime ~/.config/helix/runtime

Finishing up the installation

To make sure everything is set up as expected you should finally run the helix healthcheck via

hx --health

For more information on the information displayed in the healthcheck results refer to Healthcheck.

Building tree-sitter grammars

Tree-sitter grammars must be fetched and compiled if not pre-packaged. Fetch grammars with hx --grammar fetch (requires git) and compile them with hx --grammar build (requires a C++ compiler).

Installing language servers

Language servers can optionally be installed if you want their features (auto-complete, diagnostics etc.). Follow the instructions on the wiki page to add your language servers of choice.


(Currently not fully documented, see the keymappings list for more.)

See tutor (accessible via hx --tutor or :tutor) for a vimtutor-like introduction.


Vim-like registers can be used to yank and store text to be pasted later. Usage is similar, with " being used to select a register:

  • "ay - Yank the current selection to register a.
  • "op - Paste the text in register o after the selection.

If there is a selected register before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:

  • "hc - Store the selection in register h and then change it (delete and enter insert mode).
  • "md - Store the selection in register m and delete it.

Special Registers

Register characterContains
/Last search
:Last executed command
"Last yanked text
_Black hole

There is no special register for copying to system clipboard, instead special commands and keybindings are provided. See the keymap for the specifics. The black hole register works as a no-op register, meaning no data will be written to / read from it.


Functionality similar to vim-surround is built into helix. The keymappings have been inspired from vim-sandwich:

surround demo

  • ms - Add surround characters
  • mr - Replace surround characters
  • md - Delete surround characters

ms acts on a selection, so select the text first and use ms<char>. mr and md work on the closest pairs found and selections are not required; use counts to act in outer pairs.

It can also act on multiple selections (yay!). For example, to change every occurrence of (use) to [use]:

  • % to select the whole file
  • s to split the selections on a search term
  • Input use and hit Enter
  • mr([ to replace the parens with square brackets

Multiple characters are currently not supported, but planned.

Syntax-tree Motions

A-p, A-o, A-i, and A-n (or Alt and arrow keys) move the primary selection according to the selection's place in the syntax tree. Let's walk through an example to get familiar with them. Many languages have a syntax like so for function calls:

func(arg1, arg2, arg3)

A function call might be parsed by tree-sitter into a tree like the following.

  function: (identifier) ; func
    (arguments           ; (arg1, arg2, arg3)
      (identifier)       ; arg1
      (identifier)       ; arg2
      (identifier)))     ; arg3

Use :tree-sitter-subtree to view the syntax tree of the primary selection. In a more intuitive tree format:

      │                │
┌─────▼────┐      ┌────▼────┐
│identifier│      │arguments│
│  "func"  │ ┌────┴───┬─────┴───┐
└──────────┘ │        │         │
             │        │         │
   ┌─────────▼┐  ┌────▼─────┐  ┌▼─────────┐
   │identifier│  │identifier│  │identifier│
   │  "arg1"  │  │  "arg2"  │  │  "arg3"  │
   └──────────┘  └──────────┘  └──────────┘

Say we have a selection that wraps arg1. The selection is on the arg1 leaf in the tree above.

func([arg1], arg2, arg3)

Using A-n would select the next sibling in the syntax tree: arg2.

func(arg1, [arg2], arg3)

While A-o would expand the selection to the parent node. In the tree above we can see that we would select the arguments node.

func[(arg1, arg2, arg3)]

There is also some nuanced behavior that prevents you from getting stuck on a node with no sibling. If we have a selection on arg1, A-p would bring us to the previous child node. Since arg1 doesn't have a sibling to its left, though, we climb the syntax tree and then take the previous selection. So A-p will move the selection over to the "func" identifier.

[func](arg1, arg2, arg3)


textobject-demo textobject-treesitter-demo

  • ma - Select around the object (va in Vim, <alt-a> in Kakoune)
  • mi - Select inside the object (vi in Vim, <alt-i> in Kakoune)
Key after mi or maTextobject selected
(, [, ', etcSpecified surround pairs
mClosest surround pair

NOTE: f, c, etc need a tree-sitter grammar active for the current document and a special tree-sitter query file to work properly. Only some grammars currently have the query file implemented. Contributions are welcome!

Tree-sitter Textobject Based Navigation

Navigating between functions, classes, parameters, etc is made possible by leveraging tree-sitter and textobjects queries. For example to move to the next function use ]f, to move to previous class use [c, and so on.


See the unimpaired section of the keybind documentation for the full reference.

NOTE: This feature is dependent on tree-sitter based textobjects and therefore requires the corresponding query file to work properly.


💡 Mappings marked (LSP) require an active language server for the file.

💡 Mappings marked (TS) require a tree-sitter grammar for the filetype.

Normal mode


NOTE: Unlike Vim, f, F, t and T are not confined to the current line.

h, LeftMove leftmove_char_left
j, DownMove downmove_line_down
k, UpMove upmove_line_up
l, RightMove rightmove_char_right
wMove next word startmove_next_word_start
bMove previous word startmove_prev_word_start
eMove next word endmove_next_word_end
WMove next WORD startmove_next_long_word_start
BMove previous WORD startmove_prev_long_word_start
EMove next WORD endmove_next_long_word_end
tFind 'till next charfind_till_char
fFind next charfind_next_char
TFind 'till previous chartill_prev_char
FFind previous charfind_prev_char
GGo to line number <n>goto_line
Alt-.Repeat last motion (f, t or m)repeat_last_motion
HomeMove to the start of the linegoto_line_start
EndMove to the end of the linegoto_line_end
Ctrl-b, PageUpMove page uppage_up
Ctrl-f, PageDownMove page downpage_down
Ctrl-uMove half page uphalf_page_up
Ctrl-dMove half page downhalf_page_down
Ctrl-iJump forward on the jumplistjump_forward
Ctrl-oJump backward on the jumplistjump_backward
Ctrl-sSave the current selection to the jumplistsave_selection


rReplace with a characterreplace
RReplace with yanked textreplace_with_yanked
~Switch case of the selected textswitch_case
`Set the selected text to lower caseswitch_to_lowercase
Alt-`Set the selected text to upper caseswitch_to_uppercase
iInsert before selectioninsert_mode
aInsert after selection (append)append_mode
IInsert at the start of the lineprepend_to_line
AInsert at the end of the lineappend_to_line
oOpen new line below selectionopen_below
OOpen new line above selectionopen_above
.Repeat last insertN/A
uUndo changeundo
URedo changeredo
Alt-uMove backward in historyearlier
Alt-UMove forward in historylater
yYank selectionyank
pPaste after selectionpaste_after
PPaste before selectionpaste_before
" <reg>Select a register to yank to or paste fromselect_register
>Indent selectionindent
<Unindent selectionunindent
=Format selection (currently nonfunctional/disabled) (LSP)format_selections
dDelete selectiondelete_selection
Alt-dDelete selection, without yankingdelete_selection_noyank
cChange selection (delete and enter insert mode)change_selection
Alt-cChange selection (delete and enter insert mode, without yanking)change_selection_noyank
Ctrl-aIncrement object (number) under cursorincrement
Ctrl-xDecrement object (number) under cursordecrement
QStart/stop macro recording to the selected register (experimental)record_macro
qPlay back a recorded macro from the selected register (experimental)replay_macro


|Pipe each selection through shell command, replacing with outputshell_pipe
Alt-|Pipe each selection into shell command, ignoring outputshell_pipe_to
!Run shell command, inserting output before each selectionshell_insert_output
Alt-!Run shell command, appending output after each selectionshell_append_output
$Pipe each selection into shell command, keep selections where command returned 0shell_keep_pipe

Selection manipulation

sSelect all regex matches inside selectionsselect_regex
SSplit selection into subselections on regex matchessplit_selection
Alt-sSplit selection on newlinessplit_selection_on_newline
&Align selection in columnsalign_selections
_Trim whitespace from the selectiontrim_selections
;Collapse selection onto a single cursorcollapse_selection
Alt-;Flip selection cursor and anchorflip_selections
Alt-:Ensures the selection is in forward directionensure_selections_forward
,Keep only the primary selectionkeep_primary_selection
Alt-,Remove the primary selectionremove_primary_selection
CCopy selection onto the next line (Add cursor below)copy_selection_on_next_line
Alt-CCopy selection onto the previous line (Add cursor above)copy_selection_on_prev_line
(Rotate main selection backwardrotate_selections_backward
)Rotate main selection forwardrotate_selections_forward
Alt-(Rotate selection contents backwardrotate_selection_contents_backward
Alt-)Rotate selection contents forwardrotate_selection_contents_forward
%Select entire fileselect_all
xSelect current line, if already selected, extend to next lineextend_line_below
XExtend selection to line bounds (line-wise selection)extend_to_line_bounds
Alt-xShrink selection to line bounds (line-wise selection)shrink_to_line_bounds
JJoin lines inside selectionjoin_selections
KKeep selections matching the regexkeep_selections
Alt-KRemove selections matching the regexremove_selections
Ctrl-cComment/uncomment the selectionstoggle_comments
Alt-o, Alt-upExpand selection to parent syntax node (TS)expand_selection
Alt-i, Alt-downShrink syntax tree object selection (TS)shrink_selection
Alt-p, Alt-leftSelect previous sibling node in syntax tree (TS)select_prev_sibling
Alt-n, Alt-rightSelect next sibling node in syntax tree (TS)select_next_sibling

Search commands all operate on the / register by default. Use "<char> to operate on a different one.

/Search for regex patternsearch
?Search for previous patternrsearch
nSelect next search matchsearch_next
NSelect previous search matchsearch_prev
*Use current selection as the search patternsearch_selection

Minor modes

These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.

vEnter select (extend) modeselect_mode
gEnter goto modeN/A
mEnter match modeN/A
:Enter command modecommand_mode
zEnter view modeN/A
ZEnter sticky view modeN/A
Ctrl-wEnter window modeN/A
SpaceEnter space modeN/A

View mode

View mode is intended for scrolling and manipulating the view without changing the selection. The "sticky" variant of this mode is persistent; use the Escape key to return to normal mode after usage (useful when you're simply looking over text and not actively editing it).

z, cVertically center the linealign_view_center
tAlign the line to the top of the screenalign_view_top
bAlign the line to the bottom of the screenalign_view_bottom
mAlign the line to the middle of the screen (horizontally)align_view_middle
j, downScroll the view downwardsscroll_down
k, upScroll the view upwardsscroll_up
Ctrl-f, PageDownMove page downpage_down
Ctrl-b, PageUpMove page uppage_up
Ctrl-dMove half page downhalf_page_down
Ctrl-uMove half page uphalf_page_up

Goto mode

Jumps to various locations.

gGo to line number <n> else start of filegoto_file_start
eGo to the end of the filegoto_last_line
fGo to files in the selectiongoto_file
hGo to the start of the linegoto_line_start
lGo to the end of the linegoto_line_end
sGo to first non-whitespace character of the linegoto_first_nonwhitespace
tGo to the top of the screengoto_window_top
cGo to the middle of the screengoto_window_center
bGo to the bottom of the screengoto_window_bottom
dGo to definition (LSP)goto_definition
yGo to type definition (LSP)goto_type_definition
rGo to references (LSP)goto_reference
iGo to implementation (LSP)goto_implementation
aGo to the last accessed/alternate filegoto_last_accessed_file
mGo to the last modified/alternate filegoto_last_modified_file
nGo to next buffergoto_next_buffer
pGo to previous buffergoto_previous_buffer
.Go to last modification in current filegoto_last_modification

Match mode

Enter this mode using m from normal mode. See the relevant section in Usage for an explanation about surround and textobject usage.

mGoto matching bracket (TS)match_brackets
s <char>Surround current selection with <char>surround_add
r <from><to>Replace surround character <from> with <to>surround_replace
d <char>Delete surround character <char>surround_delete
a <object>Select around textobjectselect_textobject_around
i <object>Select inside textobjectselect_textobject_inner

TODO: Mappings for selecting syntax nodes (a superset of [).

Window mode

This layer is similar to Vim keybindings as Kakoune does not support window.

w, Ctrl-wSwitch to next windowrotate_view
v, Ctrl-vVertical right splitvsplit
s, Ctrl-sHorizontal bottom splithsplit
fGo to files in the selection in horizontal splitsgoto_file
FGo to files in the selection in vertical splitsgoto_file
h, Ctrl-h, LeftMove to left splitjump_view_left
j, Ctrl-j, DownMove to split belowjump_view_down
k, Ctrl-k, UpMove to split abovejump_view_up
l, Ctrl-l, RightMove to right splitjump_view_right
q, Ctrl-qClose current windowwclose
o, Ctrl-oOnly keep the current window, closing all the otherswonly
HSwap window to the leftswap_view_left
JSwap window downwardsswap_view_down
KSwap window upwardsswap_view_up
LSwap window to the rightswap_view_right

Space mode

This layer is a kludge of mappings, mostly pickers.

fOpen file pickerfile_picker
FOpen file picker at current working directoryfile_picker_in_current_directory
bOpen buffer pickerbuffer_picker
jOpen jumplist pickerjumplist_picker
kShow documentation for item under cursor in a popup (LSP)hover
sOpen document symbol picker (LSP)symbol_picker
SOpen workspace symbol picker (LSP)workspace_symbol_picker
gOpen document diagnostics picker (LSP)diagnostics_picker
GOpen workspace diagnostics picker (LSP)workspace_diagnostics_picker
rRename symbol (LSP)rename_symbol
aApply code action (LSP)code_action
'Open last fuzzy pickerlast_picker
wEnter window modeN/A
pPaste system clipboard after selectionspaste_clipboard_after
PPaste system clipboard before selectionspaste_clipboard_before
yJoin and yank selections to clipboardyank_joined_to_clipboard
YYank main selection to clipboardyank_main_selection_to_clipboard
RReplace selections by clipboard contentsreplace_selections_with_clipboard
/Global search in workspace folderglobal_search
?Open command palettecommand_palette

TIP: Global search displays results in a fuzzy picker, use space + ' to bring it back up after opening a file.

Displays documentation for item under cursor.

Ctrl-uScroll up
Ctrl-dScroll down


Mappings in the style of vim-unimpaired.

[dGo to previous diagnostic (LSP)goto_prev_diag
]dGo to next diagnostic (LSP)goto_next_diag
[DGo to first diagnostic in document (LSP)goto_first_diag
]DGo to last diagnostic in document (LSP)goto_last_diag
]fGo to next function (TS)goto_next_function
[fGo to previous function (TS)goto_prev_function
]cGo to next class (TS)goto_next_class
[cGo to previous class (TS)goto_prev_class
]aGo to next argument/parameter (TS)goto_next_parameter
[aGo to previous argument/parameter (TS)goto_prev_parameter
]oGo to next comment (TS)goto_next_comment
[oGo to previous comment (TS)goto_prev_comment
]tGo to next test (TS)goto_next_test
]tGo to previous test (TS)goto_prev_test
]pGo to next paragraphgoto_next_paragraph
[pGo to previous paragraphgoto_prev_paragraph
[spaceAdd newline aboveadd_newline_above
]spaceAdd newline belowadd_newline_below

Insert Mode

Insert mode bindings are somewhat minimal by default. Helix is designed to be a modal editor, and this is reflected in the user experience and internal mechanics. For example, changes to the text are only saved for undos when escaping from insert mode to normal mode. For this reason, new users are strongly encouraged to learn the modal editing paradigm to get the smoothest experience.

EscapeSwitch to normal modenormal_mode
Ctrl-rInsert a register contentinsert_register
Ctrl-w, Alt-Backspace, Ctrl-BackspaceDelete previous worddelete_word_backward
Alt-d, Alt-Delete, Ctrl-DeleteDelete next worddelete_word_forward
Ctrl-uDelete to start of linekill_to_line_start
Ctrl-kDelete to end of linekill_to_line_end
Ctrl-j, EnterInsert new lineinsert_newline
Backspace, Ctrl-hDelete previous chardelete_char_backward
Delete, Ctrl-dDelete next chardelete_char_forward

However, if you really want navigation in insert mode, this is supported. An example config that gives the ability to use arrow keys while still in insert mode:

"up" = "move_line_up"
"down" = "move_line_down"
"left" = "move_char_left"
"right" = "move_char_right"
"C-b" = "move_char_left"
"C-f" = "move_char_right"
"A-b" = "move_prev_word_end"
"C-left" = "move_prev_word_end"
"A-f" = "move_next_word_start"
"C-right" = "move_next_word_start"
"A-<" = "goto_file_start"
"A->" = "goto_file_end"
"pageup" = "page_up"
"pagedown" = "page_down"
"home" = "goto_line_start"
"C-a" = "goto_line_start"
"end" = "goto_line_end_newline"
"C-e" = "goto_line_end_newline"
"A-left" = "goto_line_start"

Select / extend mode

This mode echoes Normal mode, but changes any movements to extend selections rather than replace them. Goto motions are also changed to extend, so that vgl for example extends the selection to the end of the line.

Search is also affected. By default, n and N will remove the current selection and select the next instance of the search term. Toggling this mode before pressing n or N makes it possible to keep the current selection. Toggling it on and off during your iterative searching allows you to selectively add search terms to your selections.


Keys to use within picker. Remapping currently not supported.

Shift-Tab, Up, Ctrl-pPrevious entry
Tab, Down, Ctrl-nNext entry
PageUp, Ctrl-uPage up
PageDown, Ctrl-dPage down
HomeGo to first entry
EndGo to last entry
Ctrl-spaceFilter options
EnterOpen selected
Ctrl-sOpen horizontally
Ctrl-vOpen vertically
Ctrl-tToggle preview
Escape, Ctrl-cClose picker


Keys to use within prompt, Remapping currently not supported.

Escape, Ctrl-cClose prompt
Alt-b, Ctrl-LeftBackward a word
Ctrl-b, LeftBackward a char
Alt-f, Ctrl-RightForward a word
Ctrl-f, RightForward a char
Ctrl-e, EndMove prompt end
Ctrl-a, HomeMove prompt start
Ctrl-w, Alt-Backspace, Ctrl-BackspaceDelete previous word
Alt-d, Alt-Delete, Ctrl-DeleteDelete next word
Ctrl-uDelete to start of line
Ctrl-kDelete to end of line
backspace, Ctrl-hDelete previous char
delete, Ctrl-dDelete next char
Ctrl-sInsert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later
Ctrl-p, UpSelect previous history
Ctrl-n, DownSelect next history
Ctrl-rInsert the content of the register selected by following input char
TabSelect next completion item
BackTabSelect previous completion item
EnterOpen selected


Command mode can be activated by pressing :, similar to Vim. Built-in commands:

:quit, :qClose the current view.
:quit!, :q!Force close the current view, ignoring unsaved changes.
:open, :oOpen a file from disk into the current view.
:buffer-close, :bc, :bcloseClose the current buffer.
:buffer-close!, :bc!, :bclose!Close the current buffer forcefully, ignoring unsaved changes.
:buffer-close-others, :bco, :bcloseotherClose all buffers but the currently focused one.
:buffer-close-others!, :bco!, :bcloseother!Force close all buffers but the currently focused one.
:buffer-close-all, :bca, :bcloseallClose all buffers without quitting.
:buffer-close-all!, :bca!, :bcloseall!Force close all buffers ignoring unsaved changes without quitting.
:buffer-next, :bn, :bnextGoto next buffer.
:buffer-previous, :bp, :bprevGoto previous buffer.
:write, :wWrite changes to disk. Accepts an optional path (:write some/path.txt)
:write!, :w!Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write some/path.txt)
:new, :nCreate a new scratch buffer.
:format, :fmtFormat the file using the LSP formatter.
:indent-styleSet the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)
:line-endingSet the document's default line ending. Options: crlf, lf.
:earlier, :earJump back to an earlier point in edit history. Accepts a number of steps or a time span.
:later, :latJump to a later point in edit history. Accepts a number of steps or a time span.
:write-quit, :wq, :xWrite changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)
:write-quit!, :wq!, :x!Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)
:write-all, :waWrite changes from all buffers to disk.
:write-quit-all, :wqa, :xaWrite changes from all buffers to disk and close all views.
:write-quit-all!, :wqa!, :xa!Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).
:quit-all, :qaClose all views.
:quit-all!, :qa!Force close all views ignoring unsaved changes.
:cquit, :cqQuit with exit code (default 1). Accepts an optional integer exit code (:cq 2).
:cquit!, :cq!Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).
:themeChange the editor theme.
:clipboard-yankYank main selection into system clipboard.
:clipboard-yank-joinYank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.
:primary-clipboard-yankYank main selection into system primary clipboard.
:primary-clipboard-yank-joinYank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.
:clipboard-paste-afterPaste system clipboard after selections.
:clipboard-paste-beforePaste system clipboard before selections.
:clipboard-paste-replaceReplace selections with content of system clipboard.
:primary-clipboard-paste-afterPaste primary clipboard after selections.
:primary-clipboard-paste-beforePaste primary clipboard before selections.
:primary-clipboard-paste-replaceReplace selections with content of system primary clipboard.
:show-clipboard-providerShow clipboard provider name in status bar.
:change-current-directory, :cdChange the current working directory.
:show-directory, :pwdShow the current working directory.
:encodingSet encoding. Based on https://encoding.spec.whatwg.org.
:reloadDiscard changes and reload from the source file.
:lsp-restartRestarts the Language Server that is in use by the current doc
:tree-sitter-scopesDisplay tree sitter scopes, primarily for theming and development.
:debug-start, :dbgStart a debug session from a given template with given parameters.
:debug-remote, :dbg-tcpConnect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.
:debug-evalEvaluate expression in current debug context.
:vsplit, :vsOpen the file in a vertical split.
:vsplit-new, :vnewOpen a scratch buffer in a vertical split.
:hsplit, :hs, :spOpen the file in a horizontal split.
:hsplit-new, :hnewOpen a scratch buffer in a horizontal split.
:tutorOpen the tutorial.
:goto, :gGoto line number.
:set-language, :langSet the language of current buffer.
:set-option, :setSet a config option at runtime.
For example to disable smart case search, use :set search.smart-case false.
:get-option, :getGet the current value of a config option.
:sortSort ranges in selection.
:rsortSort ranges in selection in reverse order.
:reflowHard-wrap the current selection of lines to a given width.
:tree-sitter-subtree, :ts-subtreeDisplay tree sitter subtree under cursor, primarily for debugging queries.
:config-reloadRefresh user config.
:config-openOpen the user config.toml file.
:log-openOpen the helix log file.
:insert-outputRun shell command, inserting output after each selection.
:append-outputRun shell command, appending output after each selection.
:pipePipe each selection to the shell command.
:run-shell-command, :shRun a shell command

Language Support

The following languages and Language Servers are supported. In order to use Language Server features, you must first install the appropriate Language Server.

Check the language support in your installed helix version with hx --health.

Also see the Language Configuration docs and the Adding Languages guide for more language configuration information.

LanguageSyntax HighlightingTreesitter TextobjectsAuto IndentDefault LSP

Migrating from Vim

Helix's editing model is strongly inspired from Vim and Kakoune, and a notable difference from Vim (and the most striking similarity to Kakoune) is that Helix follows the selection → action model. This means that the whatever you are going to act on (a word, a paragraph, a line, etc) is selected first and the action itself (delete, change, yank, etc) comes second. A cursor is simply a single width selection.

See also Kakoune's Migrating from Vim and Helix's Migrating from Vim.

TODO: Mention textobjects, surround, registers


To override global configuration parameters, create a config.toml file located in your config directory:

  • Linux and Mac: ~/.config/helix/config.toml
  • Windows: %AppData%\helix\config.toml

Hint: You can easily open the config file by typing :config-open within Helix normal mode.

Example config:

theme = "onedark"

line-number = "relative"
mouse = false

insert = "bar"
normal = "block"
select = "underline"

hidden = false

You may also specify a file to use for configuration with the -c or --config CLI argument: hx -c path/to/custom-config.toml.


[editor] Section

scrolloffNumber of lines of padding around the edge of the screen when scrolling.3
mouseEnable mouse mode.true
middle-click-pasteMiddle click paste support.true
scroll-linesNumber of lines to scroll per scroll wheel step.3
shellShell to use when running external commands.Unix: ["sh", "-c"]
Windows: ["cmd", "/C"]
line-numberLine number display: absolute simply shows each line's number, while relative shows the distance from the current line. When unfocused or in insert mode, relative will still show absolute line numbers.absolute
cursorlineHighlight all lines with a cursor.false
guttersGutters to display: Available are diagnostics and line-numbers and spacer, note that diagnostics also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty["diagnostics", "line-numbers"]
auto-completionEnable automatic pop up of auto-completion.true
auto-formatEnable automatic formatting on save.true
idle-timeoutTime in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant.400
completion-trigger-lenThe min-length of word under cursor to trigger autocompletion2
auto-infoWhether to display infoboxestrue
true-colorSet to true to override automatic detection of terminal truecolor support in the event of a false negative.false
rulersList of column positions at which to display the rulers. Can be overridden by language specific rulers in languages.toml file.[]
bufferlineRenders a line at the top of the editor displaying open buffers. Can be always, never or multiple (only shown if more than one buffer is in use)never
color-modesWhether to color the mode indicator with different colors depending on the mode itselffalse

[editor.statusline] Section

Allows configuring the statusline at the bottom of the editor.

The configuration distinguishes between three areas of the status line:

[ ... ... LEFT ... ... | ... ... ... ... CENTER ... ... ... ... | ... ... RIGHT ... ... ]

Statusline elements can be defined as follows:

left = ["mode", "spinner"]
center = ["file-name"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
separator = "│"

The following elements can be configured:

modeThe current editor mode (NOR/INS/SEL)
spinnerA progress spinner indicating LSP activity
file-nameThe path/name of the opened file
file-encodingThe encoding of the opened file if it differs from UTF-8
file-line-endingThe file line endings (CRLF or LF)
file-typeThe type of the opened file
diagnosticsThe number of warnings and/or errors
selectionsThe number of active selections
positionThe cursor position
position-percentageThe cursor position as a percentage of the total number of lines
separatorThe string defined in editor.statusline.separator (defaults to "│")
spacerInserts a space between elements (multiple/contiguous spacers may be specified)

[editor.lsp] Section

display-messagesDisplay LSP progress messages below statusline1false
auto-signature-helpEnable automatic popup of signature help (parameter hints)true
display-signature-help-docsDisplay docs under signature help popuptrue

By default, a progress spinner is shown in the statusline beside the file path.

[editor.cursor-shape] Section

Defines the shape of cursor in each mode. Note that due to limitations of the terminal environment, only the primary cursor can change shape. Valid values for these options are block, bar, underline, or hidden.

normalCursor shape in normal modeblock
insertCursor shape in insert modeblock
selectCursor shape in select modeblock

[editor.file-picker] Section

Sets options for file picker and global search. All but the last key listed in the default file-picker configuration below are IgnoreOptions: whether hidden files and files listed within ignore files are ignored by (not visible in) the helix file picker and global search. There is also one other key, max-depth available, which is not defined by default.

All git related options are only enabled in a git repository.

hiddenEnables ignoring hidden files.true
parentsEnables reading ignore files from parent directories.true
ignoreEnables reading .ignore files.true
git-ignoreEnables reading .gitignore files.true
git-globalEnables reading global .gitignore, whose path is specified in git's config: core.excludefile option.true
git-excludeEnables reading .git/info/exclude files.true
max-depthSet with an integer value for maximum depth to recurse.Defaults to None.

[editor.auto-pairs] Section

Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a simple boolean value, or a specific mapping of pairs of single characters.

To disable auto-pairs altogether, set auto-pairs to false:

auto-pairs = false # defaults to `true`

The default pairs are (){}[]''""``, but these can be customized by setting auto-pairs to a TOML table:

'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
'<' = '>'

Additionally, this setting can be used in a language config. Unless the editor setting is false, this will override the editor config in documents with this language.

Example languages.toml that adds <> and removes ''

name = "rust"

'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
'<' = '>'

[editor.search] Section

Search specific options.

smart-caseEnable smart case regex searching (case insensitive unless pattern contains upper case characters)true
wrap-aroundWhether the search should wrap after depleting the matchestrue

[editor.whitespace] Section

Options for rendering whitespace with visible characters. Use :set whitespace.render all to temporarily enable visible whitespace.

renderWhether to render whitespace. May either be "all" or "none", or a table with sub-keys space, tab, and newline."none"
charactersLiteral characters to use when rendering whitespace. Sub-keys may be any of tab, space, nbsp, newline or tabpadSee example below


render = "all"
# or control each character
space = "all"
tab = "all"
newline = "none"

space = "·"
nbsp = "⍽"
tab = "→"
newline = "⏎"
tabpad = "·" # Tabs will look like "→···" (depending on tab width)

[editor.indent-guides] Section

Options for rendering vertical indent guides.

renderWhether to render indent guides.false
characterLiteral character to use for rendering the indent guide


render = true
character = "╎"


To use a theme add theme = "<name>" to your config.toml at the very top of the file before the first section or select it during runtime using :theme <name>.

Creating a theme

Create a file with the name of your theme as file name (i.e mytheme.toml) and place it in your themes directory (i.e ~/.config/helix/themes). The directory might have to be created beforehand.

The names "default" and "base16_default" are reserved for the builtin themes and cannot be overridden by user defined themes.

The default theme.toml can be found here, and user submitted themes here.

Each line in the theme file is specified as below:

key = { fg = "#ffffff", bg = "#000000", modifiers = ["bold", "italic"] }

where key represents what you want to style, fg specifies the foreground color, bg the background color, and modifiers is a list of style modifiers. bg and modifiers can be omitted to defer to the defaults.

To specify only the foreground color:

key = "#ffffff"

if the key contains a dot '.', it must be quoted to prevent it being parsed as a dotted key.

"key.key" = "#ffffff"

Color palettes

It's recommended define a palette of named colors, and refer to them from the configuration values in your theme. To do this, add a table called palette to your theme file:

"ui.background" = "white"
"ui.text" = "black"

white = "#ffffff"
black = "#000000"

Remember that the [palette] table includes all keys after its header, so you should define the palette after normal theme options.

The default palette uses the terminal's default 16 colors, and the colors names are listed below. The [palette] section in the config file takes precedence over it and is merged into the default palette.

Color Name


The following values may be used as modifiers.

Less common modifiers might not be supported by your terminal emulator.



The following is a list of scopes available to use for styling.

Syntax highlighting

These keys match tree-sitter scopes.

For a given highlight produced, styling will be determined based on the longest matching theme key. For example, the highlight function.builtin.static would match the key function.builtin rather than function.

We use a similar set of scopes as SublimeText. See also TextMate scopes.

  • attribute - Class attributes, html tag attributes

  • type - Types

    • builtin - Primitive types provided by the language (int, usize)
  • constructor

  • constant (TODO: constant.other.placeholder for %v)

    • builtin Special constants provided by the language (true, false, nil etc)
      • boolean
    • character
      • escape
    • numeric (numbers)
      • integer
      • float
  • string (TODO: string.quoted.{single, double}, string.raw/.unquoted)?

    • regexp - Regular expressions
    • special
      • path
      • url
      • symbol - Erlang/Elixir atoms, Ruby symbols, Clojure keywords
  • comment - Code comments

    • line - Single line comments (//)
    • block - Block comments (e.g. (/* */)
      • documentation - Documentation comments (e.g. /// in Rust)
  • variable - Variables

    • builtin - Reserved language variables (self, this, super, etc)
    • parameter - Function parameters
    • other
      • member - Fields of composite data types (e.g. structs, unions)
  • label

  • punctuation

    • delimiter - Commas, colons
    • bracket - Parentheses, angle brackets, etc.
    • special - String interpolation brackets.
  • keyword

    • control
      • conditional - if, else
      • repeat - for, while, loop
      • import - import, export
      • return
      • exception
    • operator - or, in
    • directive - Preprocessor directives (#if in C)
    • function - fn, func
    • storage - Keywords describing how things are stored
      • type - The type of something, class, function, var, let, etc.
      • modifier - Storage modifiers like static, mut, const, ref, etc.
  • operator - ||, +=, >

  • function

    • builtin
    • method
    • macro
    • special (preprocessor in C)
  • tag - Tags (e.g. <body> in HTML)

  • namespace

  • markup

    • heading
      • marker
      • 1, 2, 3, 4, 5, 6 - heading text for h1 through h6
    • list
      • unnumbered
      • numbered
    • bold
    • italic
    • link
      • url - urls pointed to by links
      • label - non-url link references
      • text - url and image descriptions in links
    • quote
    • raw
      • inline
      • block
  • diff - version control changes

    • plus - additions
    • minus - deletions
    • delta - modifications
      • moved - renamed or moved files/changes


These scopes are used for theming the editor interface.

  • markup
    • normal
      • completion - for completion doc popup ui
      • hover - for hover popup ui
    • heading
      • completion - for completion doc popup ui
      • hover - for hover popup ui
    • raw
      • inline
        • completion - for completion doc popup ui
        • hover - for hover popup ui
ui.background.separatorPicker separator below input line
ui.cursor.matchMatching bracket etc.
ui.cursor.primaryCursor with primary selection
ui.linenrLine numbers
ui.linenr.selectedLine number for the line the cursor is on
ui.statusline.inactiveStatusline (unfocused document)
ui.statusline.normalStatusline mode during normal mode (only if editor.color-modes is enabled)
ui.statusline.insertStatusline mode during insert mode (only if editor.color-modes is enabled)
ui.statusline.selectStatusline mode during select mode (only if editor.color-modes is enabled)
ui.statusline.separatorSeparator character in statusline
ui.popupDocumentation popups (e.g space-k)
ui.popup.infoPrompt for multiple key options
ui.windowBorder lines separating splits
ui.helpDescription box for commands
ui.textCommand prompts, popup text, etc.
ui.text.infoThe key: command text in ui.popup.info boxes
ui.virtual.rulerRuler columns (see the editor.rulers config)
ui.virtual.whitespaceVisible white-space characters
ui.virtual.indent-guideVertical indent width guides
ui.menuCode and command completion menus
ui.menu.selectedSelected autocomplete item
ui.menu.scrollfg sets thumb color, bg sets track color of scrollbar
ui.selectionFor selections in the editing area
ui.cursorline.primaryThe line of the primary cursor
ui.cursorline.secondaryThe lines of any other cursors
warningDiagnostics warning (gutter)
errorDiagnostics error (gutter)
infoDiagnostics info (gutter)
hintDiagnostics hint (gutter)
diagnosticDiagnostics fallback style (editing area)
diagnostic.hintDiagnostics hint (editing area)
diagnostic.infoDiagnostics info (editing area)
diagnostic.warningDiagnostics warning (editing area)
diagnostic.errorDiagnostics error (editing area)

You can check compliance to spec with

cargo xtask themelint onedark  # replace onedark with <name>

Key Remapping

One-way key remapping is temporarily supported via a simple TOML configuration file. (More powerful solutions such as rebinding via commands will be available in the future).

To remap keys, write a config.toml file in your helix configuration directory (default ~/.config/helix in Linux systems) with a structure like this:

# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
C-s = ":w" # Maps the Control-s to the typable command :w which is an alias for :write (save file)
C-o = ":open ~/.config/helix/config.toml" # Maps the Control-o to opening of the helix config file
a = "move_char_left" # Maps the 'a' key to the move_char_left command
w = "move_line_up" # Maps the 'w' key move_line_up
"C-S-esc" = "extend_line" # Maps Control-Shift-Escape to extend_line
g = { a = "code_action" } # Maps `ga` to show possible code actions
"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode

"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
j = { k = "normal_mode" } # Maps `jk` to exit insert mode

NOTE: Typable commands can also be remapped, remember to keep the : prefix to indicate it's a typable command.

Control, Shift and Alt modifiers are encoded respectively with the prefixes C-, S- and A-. Special keys are encoded as follows:

Key nameRepresentation
Page Up"pageup"
Page Down"pagedown"

Keys can be disabled by binding them to the no_op command.

Commands can be found at Keymap Commands.

Commands can also be found in the source code at helix-term/src/commands.rs at the invocation of static_commands! macro and the TypableCommandList.


Language-specific settings and settings for language servers are configured in languages.toml files.

languages.toml files

There are three possible languages.toml files. The first is compiled into Helix and lives in the Helix repository. This provides the default configurations for languages and language servers.

You may define a languages.toml in your configuration directory which overrides values from the built-in language configuration. For example to disable auto-LSP-formatting in Rust:

# in <config_dir>/helix/languages.toml

name = "rust"
auto-format = false

Language configuration may also be overridden local to a project by creating a languages.toml file under a .helix directory. Its settings will be merged with the language configuration in the configuration directory and the built-in configuration.

Language configuration

Each language is configured by adding a [[language]] section to a languages.toml file. For example:

name = "mylang"
scope = "source.mylang"
injection-regex = "^mylang$"
file-types = ["mylang", "myl"]
comment-token = "#"
indent = { tab-width = 2, unit = "  " }
language-server = { command = "mylang-lsp", args = ["--stdio"] }
formatter = { command = "mylang-formatter" , args = ["--stdin"] }

These configuration keys are available:

nameThe name of the language
scopeA string like source.js that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually source.<name> or text.<name> in case of markup languages
injection-regexregex pattern that will be tested against a language name in order to determine whether this language should be used for a potential language injection site.
file-typesThe filetypes of the language, for example ["yml", "yaml"]. Extensions and full file names are supported.
shebangsThe interpreters from the shebang line, for example ["sh", "bash"]
rootsA set of marker files to look for when trying to find the workspace root. For example Cargo.lock, yarn.lock
auto-formatWhether to autoformat this language when saving
diagnostic-severityMinimal severity of diagnostic for it to be displayed. (Allowed values: Error, Warning, Info, Hint)
comment-tokenThe token to use as a comment-token
indentThe indent to use. Has sub keys tab-width and unit
language-serverThe Language Server to run. See the Language Server configuration section below.
configLanguage Server configuration
grammarThe tree-sitter grammar to use (defaults to the value of name)
formatterThe formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout
max-line-lengthMaximum line length. Used for the :reflow command

Language Server configuration

The language-server field takes the following keys:

commandThe name of the language server binary to execute. Binaries must be in $PATH
argsA list of arguments to pass to the language server binary
timeoutThe maximum time a request to the language server may take, in seconds. Defaults to 20
language-idThe language name to pass to the language server. Some language servers support multiple languages and use this field to determine which one is being served in a buffer

The top-level config field is used to configure the LSP initialization options. A format sub-table within config can be used to pass extra formatting options to Document Formatting Requests. For example with typescript:

name = "typescript"
auto-format = true
# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }

Tree-sitter grammar configuration

The source for a language's tree-sitter grammar is specified in a [[grammar]] section in languages.toml. For example:

name = "mylang"
source = { git = "https://github.com/example/mylang", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }

Grammar configuration takes these keys:

nameThe name of the tree-sitter grammar
sourceThe method of fetching the grammar - a table with a schema defined below

Where source is a table with either these keys when using a grammar from a git repository:

gitA git remote URL from which the grammar should be cloned
revThe revision (commit hash or tag) which should be fetched
subpathA path within the grammar directory which should be built. Some grammar repositories host multiple grammars (for example tree-sitter-typescript and tree-sitter-ocaml) in subdirectories. This key is used to point hx --grammar build to the correct path for compilation. When omitted, the root of repository is used

Choosing grammars

You may use a top-level use-grammars key to control which grammars are fetched and built when using hx --grammar fetch and hx --grammar build.

# Note: this key must come **before** the [[language]] and [[grammar]] sections
use-grammars = { only = [ "rust", "c", "cpp" ] }
# or
use-grammars = { except = [ "yaml", "json" ] }

When omitted, all grammars are fetched and built.


This section contains guides for adding new language server configurations, tree-sitter grammars, textobject queries, etc.

Adding languages

Language configuration

To add a new language, you need to add a [[language]] entry to the languages.toml (see the language configuration section).

When adding a new language or Language Server configuration for an existing language, run cargo xtask docgen to add the new configuration to the Language Support docs before creating a pull request. When adding a Language Server configuration, be sure to update the Language Server Wiki with installation notes.

Grammar configuration

If a tree-sitter grammar is available for the language, add a new [[grammar]] entry to languages.toml.

You may use the source.path key rather than source.git with an absolute path to a locally available grammar for testing, but switch to source.git before submitting a pull request.


For a language to have syntax-highlighting and indentation among other things, you have to add queries. Add a directory for your language with the path runtime/queries/<name>/. The tree-sitter website gives more info on how to write queries.

NOTE: When evaluating queries, the first matching query takes precedence, which is different from other editors like Neovim where the last matching query supersedes the ones before it. See this issue for an example.

Common Issues

  • If you get errors when running after switching branches, you may have to update the tree-sitter grammars. Run hx --grammar fetch to fetch the grammars and hx --grammar build to build any out-of-date grammars.

  • If a parser is segfaulting or you want to remove the parser, make sure to remove the compiled parser in runtime/grammar/<name>.so

Adding Textobject Queries

Textobjects that are language specific (like functions, classes, etc) require an accompanying tree-sitter grammar and a textobjects.scm query file to work properly. Tree-sitter allows us to query the source code syntax tree and capture specific parts of it. The queries are written in a lisp dialect. More information on how to write queries can be found in the official tree-sitter documentation.

Query files should be placed in runtime/queries/{language}/textobjects.scm when contributing. Note that to test the query files locally you should put them under your local runtime directory (~/.config/helix/runtime on Linux for example).

The following captures are recognized:

Capture Name

Example query files can be found in the helix GitHub repository.

Queries for Textobject Based Navigation

Tree-sitter based navigation is done using captures in the following order:

  • object.movement
  • object.around
  • object.inside

For example if a function.around capture has been already defined for a language in it's textobjects.scm file, function navigation should also work automatically. function.movement should be defined only if the node captured by function.around doesn't make sense in a navigation context.

Adding Indent Queries

Helix uses tree-sitter to correctly indent new lines. This requires a tree-sitter grammar and an indent.scm query file placed in runtime/queries/{language}/indents.scm. The indentation for a line is calculated by traversing the syntax tree from the lowest node at the beginning of the new line. Each of these nodes contributes to the total indent when it is captured by the query (in what way depends on the name of the capture).

Note that it matters where these added indents begin. For example, multiple indent level increases that start on the same line only increase the total indent level by 1.


Added indents don't always apply to the whole node. For example, in most cases when a node should be indented, we actually only want everything except for its first line to be indented. For this, there are several scopes (more scopes may be added in the future if required):

  • all: This scope applies to the whole captured node. This is only different from tail when the captured node is the first node on its line.

  • tail: This scope applies to everything except for the first line of the captured node.

Every capture type has a default scope which should do the right thing in most situations. When a different scope is required, this can be changed by using a #set! declaration anywhere in the pattern:

  right: (_) @indent
  (#set! "scope" "all"))

Capture Types

  • @indent (default scope tail): Increase the indent level by 1. Multiple occurrences in the same line don't stack. If there is at least one @indent and one @outdent capture on the same line, the indent level isn't changed at all.

  • @outdent (default scope all): Decrease the indent level by 1. The same rules as for @indent apply.


In some cases, an S-expression cannot express exactly what pattern should be matched. For that, tree-sitter allows for predicates to appear anywhere within a pattern, similar to how #set! declarations work:

  (child_kind) @indent
  (#predicate? arg1 arg2 ...)

The number of arguments depends on the predicate that's used. Each argument is either a capture (@name) or a string ("some string"). The following predicates are supported by tree-sitter:

  • #eq?/#not-eq?: The first argument (a capture) must/must not be equal to the second argument (a capture or a string).

  • #match?/#not-match?: The first argument (a capture) must/must not match the regex given in the second argument (a string).

Additionally, we support some custom predicates for indent queries:

  • #not-kind-eq?: The kind of the first argument (a capture) must not be equal to the second argument (a string).

  • #same-line?/#not-same-line?: The captures given by the 2 arguments must/must not start on the same line.