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

Packaging status


A Homebrew tap is available:

brew tap helix-editor/helix
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.

Arch Linux

Releases are available in the community repository.

Packages are also available on AUR:

Build from source

git clone --recurse-submodules --shallow-submodules -j8
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 overriden via the HELIX_RUNTIME environment variable.


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

See tutor.txt (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 seletions (yay!). For example, to change every occurance 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.


Currently supported: word, surround, function, class, parameter.

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

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 !

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.

TODO: Mention texobjects, 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


[editor] section of the config.

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, relative)absolute
smart-caseEnable smart case regex searching (case insensitive unless pattern contains upper case characters)true
auto-pairsEnable automatic insertion of pairs to parenthese, brackets, etc.true
auto-completionEnable automatic pop up of auto-completion.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

[editor.filepicker] section of the config. 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.

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.


To display all language server messages in the status line add the following to your config.toml:

display-messages = true


First you'll need to place selected themes in your themes directory (i.e ~/.config/helix/themes), the directory might have to be created beforehand.

To use a custom theme add theme = <name> to your config.toml or override it during runtime using :theme <name>.

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

Creating a theme

First 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).

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.

  • type - Types

    • builtin - Primitive types provided by the language (int, usize)
  • 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)
    • function (TODO: ?)
  • label

  • punctuation

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

    • control
      • conditional - if, else
      • repeat - for, while, loop
      • import - import, export
      • (TODO: return?)
    • directive - Preprocessor directives (#if in C)
    • function - fn, func
  • operator - ||, +=, >, or

  • function

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

  • namespace


These scopes are used for theming the editor interface.

ui.cursor.matchMatching bracket etc.
ui.cursor.primaryCursor with primary selection
ui.statusline.inactiveStatusline (unfocused document)
ui.selectionFor selections in the editing area
warningDiagnostics warning (gutter)
errorDiagnostics error (gutter)
infoDiagnostics info (gutter)
hintDiagnostics hint (gutter)
diagnosticFor text in editing area


  • 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
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
PageUpMove page uppage_up
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
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


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 changeN/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 (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


|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

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
,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
XExtend selection to line bounds (line-wise selection)extend_to_line_bounds
Expand selection to parent syntax node TODO: pick a key (TS)expand_selection
JJoin lines inside selectionjoin_selections
KKeep selections matching the regexkeep_selections
Alt-KRemove selections matching the regexremove_selections
$Pipe each selection into shell command, keep selections where command returned 0shell_keep_pipe
Ctrl-cComment/uncomment the selectionstoggle_comments
/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.

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
fMove page downpage_down
bMove page uppage_up
dMove half page downhalf_page_down
uMove half page uphalf_page_up

Goto mode

Jumps to various locations.

gGo to the start of the filegoto_file_start
eGo to the end of the filegoto_last_line
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
mGo to the middle of the screengoto_window_middle
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
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 relavant 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
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

Space mode

This layer is a kludge of mappings, mostly pickers.

fOpen file pickerfile_picker
bOpen buffer pickerbuffer_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
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

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
[spaceAdd newline aboveadd_newline_above
]spaceAdd newline belowadd_newline_below

Insert Mode

EscapeSwitch to normal modenormal_mode
Ctrl-rInsert a register contentinsert_register
Ctrl-wDelete previous worddelete_word_backward
Alt-dDelete next worddelete_word_forward
Alt-b, Alt-LeftBackward a wordmove_prev_word_end
Ctrl-b, LeftBackward a charmove_char_left
Alt-f, Alt-RightForward a wordmove_next_word_start
Ctrl-f, RightForward a charmove_char_right
Ctrl-e, Endmove to line endgoto_line_end_newline
Ctrl-a, Homemove to line startgoto_line_start
Ctrl-udelete to start of linekill_to_line_start
Ctrl-kdelete to end of linekill_to_line_end
backspace, Ctrl-hdelete previous chardelete_char_backward
delete, Ctrl-ddelete previous chardelete_char_forward
Ctrl-p, Upmove to previous linemove_line_up
Ctrl-n, Downmove to next linemove_line_down

Select / extend mode

I'm still pondering whether to keep this mode or not. It changes movement commands (including goto) to extend the existing selection instead of replacing it.

NOTE: It's a bit confusing at the moment because extend hasn't been implemented for all movement commands yet.


Keys to use within picker. Remapping currently not supported.

Up, Ctrl-k, Ctrl-pPrevious entry
Down, Ctrl-j, Ctrl-nNext entry
Ctrl-spaceFilter options
EnterOpen selected
Ctrl-sOpen horizontally
Ctrl-vOpen vertically
Escape, Ctrl-cClose picker


Keys to use within prompt, Remapping currently not supported.

Escape, Ctrl-cClose prompt
Alt-b, Alt-LeftBackward a word
Ctrl-b, LeftBackward a char
Alt-f, Alt-RightForward a word
Ctrl-f, RightForward a char
Ctrl-e, EndMove prompt end
Ctrl-a, HomeMove prompt start
Ctrl-wDelete previous word
Alt-dDelete 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
TabSelect next completion item
BackTabSelect previous completion item
EnterOpen selected

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 ''
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

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

Key nameRepresentation

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/ at the invocation of commands! macro.



Language-specific settings and settings for particular language servers can be configured in a languages.toml file placed in your configuration directory. Helix actually uses two languages.toml files, the first one is in the main helix repository; it contains the default settings for each language and is included in the helix binary at compile time. Users who want to see the available settings and options can either reference the helix repo's languages.toml file, or consult the table in the adding languages section.

Changes made to the languages.toml file in a user's configuration directory are merged with helix's defaults on start-up, such that a user's settings will take precedence over defaults in the event of a collision. For example, the default languages.toml sets rust's auto-format to true. If a user wants to disable auto-format, they can change the languages.toml in their configuration directory to make the rust entry read like the example below; the new key/value pair auto-format = false will override the default when the two sets of settings are merged on start-up:

# in <config_dir>/helix/languages.toml

name = "rust"
auto-format = false


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

Adding languages


To add a new langauge, you should first add a tree-sitter submodule. To do this, you can run the command

git submodule add -f <repository> helix-syntax/languages/tree-sitter-<name>

For example, to add tree-sitter-ocaml you would run

git submodule add -f helix-syntax/languages/tree-sitter-ocaml

Make sure the submodule is shallow by doing

git config -f .gitmodules submodule.helix-syntax/languages/tree-sitter-<name>.shallow true

or you can manually add shallow = true to .gitmodules.


Next, you need to add the language to the languages.toml found in the root of the repository; this languages.toml file is included at compilation time, and is distinct from the language.toml file in the user's configuration directory.

These are the available keys and descriptions for the file.

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"]
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
comment-tokenThe token to use as a comment-token
indentThe indent to use. Has sub keys tab-width and unit
configLanguage server configuration


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.

Common Issues

  • If you get errors when building after switching branches, you may have to remove or update tree-sitter submodules. You can update submodules by running

    git submodule sync; git submodule update --init
  • Make sure to not use the --remote flag. To remove submodules look inside the .gitmodules and remove directories that are not present inside of it.

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

  • The indents query is indents.toml, not indents.scm. See this issue for more information.

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.