Helix
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.
Refer the FAQ for common questions.
Installation
We provide pre-built binaries on the GitHub Releases page.
OSX
A Homebrew tap is available:
brew tap helix-editor/helix
brew install helix
Linux
NixOS
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. With Cachix
installed, 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.
OS | command |
---|---|
windows | xcopy runtime %AppData%/helix/runtime |
linux/macos | ln -s $PWD/runtime ~/.config/helix/runtime |
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).
Usage
(Currently not fully documented, see the keymappings list for more.)
See tutor.txt (accessible via hx --tutor
or :tutor
) for a vimtutor-like introduction.
Registers
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 registera
."op
- Paste the text in registero
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 registerh
and then change it (delete and enter insert mode)."md
- Store the selection in registerm
and delete it.
Special Registers
Register character | Contains |
---|---|
/ | 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.
Surround
Functionality similar to vim-surround is built into helix. The keymappings have been inspired from vim-sandwich:
ms
- Add surround charactersmr
- Replace surround charactersmd
- 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 files
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.
Textobjects
Currently supported: word
, surround
, function
, class
, parameter
.
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 ma | Textobject selected |
---|---|
w | Word |
W | WORD |
( , [ , ' , etc | Specified surround pairs |
m | Closest surround pair |
f | Function |
c | Class |
a | Argument/parameter |
o | Comment |
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.
Keymap
- Mappings marked (LSP) require an active language server for the file.
- Mappings marked (TS) require a tree-sitter grammar for the filetype.
Normal mode
Movement
NOTE: Unlike vim,
f
,F
,t
andT
are not confined to the current line.
Key | Description | Command |
---|---|---|
h , Left | Move left | move_char_left |
j , Down | Move down | move_line_down |
k , Up | Move up | move_line_up |
l , Right | Move right | move_char_right |
w | Move next word start | move_next_word_start |
b | Move previous word start | move_prev_word_start |
e | Move next word end | move_next_word_end |
W | Move next WORD start | move_next_long_word_start |
B | Move previous WORD start | move_prev_long_word_start |
E | Move next WORD end | move_next_long_word_end |
t | Find 'till next char | find_till_char |
f | Find next char | find_next_char |
T | Find 'till previous char | till_prev_char |
F | Find previous char | find_prev_char |
G | Go to line number <n> | goto_line |
Alt-. | Repeat last motion (f , t or m ) | repeat_last_motion |
Home | Move to the start of the line | goto_line_start |
End | Move to the end of the line | goto_line_end |
Ctrl-b , PageUp | Move page up | page_up |
Ctrl-f , PageDown | Move page down | page_down |
Ctrl-u | Move half page up | half_page_up |
Ctrl-d | Move half page down | half_page_down |
Ctrl-i | Jump forward on the jumplist | jump_forward |
Ctrl-o | Jump backward on the jumplist | jump_backward |
Ctrl-s | Save the current selection to the jumplist | save_selection |
Changes
Key | Description | Command |
---|---|---|
r | Replace with a character | replace |
R | Replace with yanked text | replace_with_yanked |
~ | Switch case of the selected text | switch_case |
` | Set the selected text to lower case | switch_to_lowercase |
Alt-` | Set the selected text to upper case | switch_to_uppercase |
i | Insert before selection | insert_mode |
a | Insert after selection (append) | append_mode |
I | Insert at the start of the line | prepend_to_line |
A | Insert at the end of the line | append_to_line |
o | Open new line below selection | open_below |
O | Open new line above selection | open_above |
. | Repeat last change | N/A |
u | Undo change | undo |
U | Redo change | redo |
Alt-u | Move backward in history | earlier |
Alt-U | Move forward in history | later |
y | Yank selection | yank |
p | Paste after selection | paste_after |
P | Paste before selection | paste_before |
" <reg> | Select a register to yank to or paste from | select_register |
> | Indent selection | indent |
< | Unindent selection | unindent |
= | Format selection (currently nonfunctional/disabled) (LSP) | format_selections |
d | Delete selection | delete_selection |
Alt-d | Delete selection, without yanking | delete_selection_noyank |
c | Change selection (delete and enter insert mode) | change_selection |
Alt-c | Change selection (delete and enter insert mode, without yanking) | change_selection_noyank |
Ctrl-a | Increment object (number) under cursor | increment |
Ctrl-x | Decrement object (number) under cursor | decrement |
Q | Start/stop macro recording to the selected register (experimental) | record_macro |
q | Play back a recorded macro from the selected register (experimental) | replay_macro |
Shell
Key | Description | Command |
---|---|---|
| | Pipe each selection through shell command, replacing with output | shell_pipe |
Alt-| | Pipe each selection into shell command, ignoring output | shell_pipe_to |
! | Run shell command, inserting output before each selection | shell_insert_output |
Alt-! | Run shell command, appending output after each selection | shell_append_output |
$ | Pipe each selection into shell command, keep selections where command returned 0 | shell_keep_pipe |
Selection manipulation
Key | Description | Command |
---|---|---|
s | Select all regex matches inside selections | select_regex |
S | Split selection into subselections on regex matches | split_selection |
Alt-s | Split selection on newlines | split_selection_on_newline |
& | Align selection in columns | align_selections |
_ | Trim whitespace from the selection | trim_selections |
; | Collapse selection onto a single cursor | collapse_selection |
Alt-; | Flip selection cursor and anchor | flip_selections |
Alt-: | Ensures the selection is in forward direction | ensure_selections_forward |
, | Keep only the primary selection | keep_primary_selection |
Alt-, | Remove the primary selection | remove_primary_selection |
C | Copy selection onto the next line (Add cursor below) | copy_selection_on_next_line |
Alt-C | Copy selection onto the previous line (Add cursor above) | copy_selection_on_prev_line |
( | Rotate main selection backward | rotate_selections_backward |
) | Rotate main selection forward | rotate_selections_forward |
Alt-( | Rotate selection contents backward | rotate_selection_contents_backward |
Alt-) | Rotate selection contents forward | rotate_selection_contents_forward |
% | Select entire file | select_all |
x | Select current line, if already selected, extend to next line | extend_line |
X | Extend selection to line bounds (line-wise selection) | extend_to_line_bounds |
J | Join lines inside selection | join_selections |
K | Keep selections matching the regex | keep_selections |
Alt-K | Remove selections matching the regex | remove_selections |
Ctrl-c | Comment/uncomment the selections | toggle_comments |
Alt-o , Alt-up | Expand selection to parent syntax node (TS) | expand_selection |
Alt-i , Alt-down | Shrink syntax tree object selection (TS) | shrink_selection |
Alt-p , Alt-left | Select previous sibling node in syntax tree (TS) | select_prev_sibling |
Alt-n , Alt-right | Select next sibling node in syntax tree (TS) | select_next_sibling |
Search
Search commands all operate on the /
register by default. Use "<char>
to operate on a different one.
Key | Description | Command |
---|---|---|
/ | Search for regex pattern | search |
? | Search for previous pattern | rsearch |
n | Select next search match | search_next |
N | Select previous search match | search_prev |
* | Use current selection as the search pattern | search_selection |
Minor modes
These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.
Key | Description | Command |
---|---|---|
v | Enter select (extend) mode | select_mode |
g | Enter goto mode | N/A |
m | Enter match mode | N/A |
: | Enter command mode | command_mode |
z | Enter view mode | N/A |
Z | Enter sticky view mode | N/A |
Ctrl-w | Enter window mode | N/A |
Space | Enter space mode | N/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).
Key | Description | Command |
---|---|---|
z , c | Vertically center the line | align_view_center |
t | Align the line to the top of the screen | align_view_top |
b | Align the line to the bottom of the screen | align_view_bottom |
m | Align the line to the middle of the screen (horizontally) | align_view_middle |
j , down | Scroll the view downwards | scroll_down |
k , up | Scroll the view upwards | scroll_up |
Ctrl-f , PageDown | Move page down | page_down |
Ctrl-b , PageUp | Move page up | page_up |
Ctrl-d | Move half page down | half_page_down |
Ctrl-u | Move half page up | half_page_up |
Goto mode
Jumps to various locations.
Key | Description | Command |
---|---|---|
g | Go to line number <n> else start of file | goto_file_start |
e | Go to the end of the file | goto_last_line |
f | Go to files in the selection | goto_file |
h | Go to the start of the line | goto_line_start |
l | Go to the end of the line | goto_line_end |
s | Go to first non-whitespace character of the line | goto_first_nonwhitespace |
t | Go to the top of the screen | goto_window_top |
c | Go to the middle of the screen | goto_window_center |
b | Go to the bottom of the screen | goto_window_bottom |
d | Go to definition (LSP) | goto_definition |
y | Go to type definition (LSP) | goto_type_definition |
r | Go to references (LSP) | goto_reference |
i | Go to implementation (LSP) | goto_implementation |
a | Go to the last accessed/alternate file | goto_last_accessed_file |
m | Go to the last modified/alternate file | goto_last_modified_file |
n | Go to next buffer | goto_next_buffer |
p | Go to previous buffer | goto_previous_buffer |
. | Go to last modification in current file | goto_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.
Key | Description | Command |
---|---|---|
m | Goto 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 textobject | select_textobject_around |
i <object> | Select inside textobject | select_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.
Key | Description | Command |
---|---|---|
w , Ctrl-w | Switch to next window | rotate_view |
v , Ctrl-v | Vertical right split | vsplit |
s , Ctrl-s | Horizontal bottom split | hsplit |
f | Go to files in the selection in horizontal splits | goto_file |
F | Go to files in the selection in vertical splits | goto_file |
h , Ctrl-h , Left | Move to left split | jump_view_left |
j , Ctrl-j , Down | Move to split below | jump_view_down |
k , Ctrl-k , Up | Move to split above | jump_view_up |
l , Ctrl-l , Right | Move to right split | jump_view_right |
q , Ctrl-q | Close current window | wclose |
o , Ctrl-o | Only keep the current window, closing all the others | wonly |
Space mode
This layer is a kludge of mappings, mostly pickers.
Key | Description | Command |
---|---|---|
f | Open file picker | file_picker |
b | Open buffer picker | buffer_picker |
k | Show documentation for item under cursor in a popup (LSP) | hover |
s | Open document symbol picker (LSP) | symbol_picker |
S | Open workspace symbol picker (LSP) | workspace_symbol_picker |
r | Rename symbol (LSP) | rename_symbol |
a | Apply code action (LSP) | code_action |
' | Open last fuzzy picker | last_picker |
w | Enter window mode | N/A |
p | Paste system clipboard after selections | paste_clipboard_after |
P | Paste system clipboard before selections | paste_clipboard_before |
y | Join and yank selections to clipboard | yank_joined_to_clipboard |
Y | Yank main selection to clipboard | yank_main_selection_to_clipboard |
R | Replace selections by clipboard contents | replace_selections_with_clipboard |
/ | Global search in workspace folder | global_search |
? | Open command palette | command_palette |
TIP: Global search displays results in a fuzzy picker, use
space + '
to bring it back up after opening a file.
Popup
Displays documentation for item under cursor.
Key | Description |
---|---|
Ctrl-u | Scroll up |
Ctrl-d | Scroll down |
Unimpaired
Mappings in the style of vim-unimpaired.
Key | Description | Command |
---|---|---|
[d | Go to previous diagnostic (LSP) | goto_prev_diag |
]d | Go to next diagnostic (LSP) | goto_next_diag |
[D | Go to first diagnostic in document (LSP) | goto_first_diag |
]D | Go to last diagnostic in document (LSP) | goto_last_diag |
]f | Go to next function (TS) | goto_next_function |
[f | Go to previous function (TS) | goto_prev_function |
]c | Go to next class (TS) | goto_next_class |
[c | Go to previous class (TS) | goto_prev_class |
]a | Go to next argument/parameter (TS) | goto_next_parameter |
[a | Go to previous argument/parameter (TS) | goto_prev_parameter |
]o | Go to next comment (TS) | goto_next_comment |
[o | Go to previous comment (TS) | goto_prev_comment |
]p | Go to next paragraph | goto_next_paragraph |
[p | Go to previous paragraph | goto_prev_paragraph |
[space | Add newline above | add_newline_above |
]space | Add newline below | add_newline_below |
Insert Mode
We support many readline/emacs style bindings in insert mode for convenience. These can be helpful for making simple modifications without escaping to normal mode, but beware that you will not have an undo-able "save point" until you return to normal mode.
Key | Description | Command |
---|---|---|
Escape | Switch to normal mode | normal_mode |
Ctrl-x | Autocomplete | completion |
Ctrl-r | Insert a register content | insert_register |
Ctrl-w , Alt-Backspace | Delete previous word | delete_word_backward |
Alt-d | Delete next word | delete_word_forward |
Alt-b , Ctrl-Left | Backward a word | move_prev_word_end |
Ctrl-b , Left | Backward a char | move_char_left |
Alt-f , Ctrl-Right | Forward a word | move_next_word_start |
Ctrl-f , Right | Forward a char | move_char_right |
Ctrl-e , End | Move to line end | goto_line_end_newline |
Ctrl-a , Home | Move to line start | goto_line_start |
Ctrl-u | Delete to start of line | kill_to_line_start |
Ctrl-k | Delete to end of line | kill_to_line_end |
Ctrl-j , Enter | Insert new line | insert_newline |
Backspace , Ctrl-h | Delete previous char | delete_char_backward |
Delete , Ctrl-d | Delete next char | delete_char_forward |
Ctrl-p , Up | Move to previous line | move_line_up |
Ctrl-n , Down | Move to next line | move_line_down |
PageUp | Move one page up | page_up |
PageDown | Move one page down | page_down |
Alt-> | Go to end of buffer | goto_file_end |
Alt-< | Go to start of buffer | goto_file_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.
Picker
Keys to use within picker. Remapping currently not supported.
Key | Description |
---|---|
Up , Ctrl-p | Previous entry |
PageUp , Ctrl-u | Page up |
Down , Ctrl-n | Next entry |
PageDown , Ctrl-d | Page down |
Home | Go to first entry |
End | Go to last entry |
Ctrl-space | Filter options |
Enter | Open selected |
Ctrl-s | Open horizontally |
Ctrl-v | Open vertically |
Escape , Ctrl-c | Close picker |
Prompt
Keys to use within prompt, Remapping currently not supported.
Key | Description |
---|---|
Escape , Ctrl-c | Close prompt |
Alt-b , Alt-Left | Backward a word |
Ctrl-b , Left | Backward a char |
Alt-f , Alt-Right | Forward a word |
Ctrl-f , Right | Forward a char |
Ctrl-e , End | Move prompt end |
Ctrl-a , Home | Move prompt start |
Ctrl-w | Delete previous word |
Alt-d | Delete next word |
Ctrl-u | Delete to start of line |
Ctrl-k | Delete to end of line |
backspace , Ctrl-h | Delete previous char |
delete , Ctrl-d | Delete next char |
Ctrl-s | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
Ctrl-p , Up | Select previous history |
Ctrl-n , Down | Select next history |
Tab | Select next completion item |
BackTab | Select previous completion item |
Enter | Open selected |
Commands
Command mode can be activated by pressing :
, similar to vim. Built-in commands:
Name | Description |
---|---|
:quit , :q | Close the current view. |
:quit! , :q! | Close the current view forcefully (ignoring unsaved changes). |
:open , :o | Open a file from disk into the current view. |
:buffer-close , :bc , :bclose | Close the current buffer. |
:buffer-close! , :bc! , :bclose! | Close the current buffer forcefully (ignoring unsaved changes). |
:buffer-close-others , :bco , :bcloseother | Close all buffers but the currently focused one. |
:buffer-close-others! , :bco! , :bcloseother! | Close all buffers but the currently focused one. |
:buffer-close-all , :bca , :bcloseall | Close all buffers, without quitting. |
:buffer-close-all! , :bca! , :bcloseall! | Close all buffers forcefully (ignoring unsaved changes), without quitting. |
:buffer-next , :bn , :bnext | Go to next buffer. |
:buffer-previous , :bp , :bprev | Go to previous buffer. |
:write , :w | Write changes to disk. Accepts an optional path (:write some/path.txt) |
:write! , :w! | Write changes to disk forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt) |
:new , :n | Create a new scratch buffer. |
:format , :fmt | Format the file using the LSP formatter. |
:indent-style | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
:line-ending | Set the document's default line ending. Options: crlf, lf. |
:earlier , :ear | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
:later , :lat | Jump to a later point in edit history. Accepts a number of steps or a time span. |
:write-quit , :wq , :x | Write 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 , :wa | Write changes from all views to disk. |
:write-quit-all , :wqa , :xa | Write changes from all views to disk and close all views. |
:write-quit-all! , :wqa! , :xa! | Write changes from all views to disk and close all views forcefully (ignoring unsaved changes). |
:quit-all , :qa | Close all views. |
:quit-all! , :qa! | Close all views forcefully (ignoring unsaved changes). |
:cquit , :cq | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
:cquit! , :cq! | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). |
:theme | Change the editor theme. |
:clipboard-yank | Yank main selection into system clipboard. |
:clipboard-yank-join | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
:primary-clipboard-yank | Yank main selection into system primary clipboard. |
:primary-clipboard-yank-join | Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline. |
:clipboard-paste-after | Paste system clipboard after selections. |
:clipboard-paste-before | Paste system clipboard before selections. |
:clipboard-paste-replace | Replace selections with content of system clipboard. |
:primary-clipboard-paste-after | Paste primary clipboard after selections. |
:primary-clipboard-paste-before | Paste primary clipboard before selections. |
:primary-clipboard-paste-replace | Replace selections with content of system primary clipboard. |
:show-clipboard-provider | Show clipboard provider name in status bar. |
:change-current-directory , :cd | Change the current working directory. |
:show-directory , :pwd | Show the current working directory. |
:encoding | Set encoding based on https://encoding.spec.whatwg.org |
:reload | Discard changes and reload from the source file. |
:tree-sitter-scopes | Display tree sitter scopes, primarily for theming and development. |
:debug-start , :dbg | Start a debug session from a given template with given parameters. |
:debug-remote , :dbg-tcp | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
:debug-eval | Evaluate expression in current debug context. |
:vsplit , :vs | Open the file in a vertical split. |
:vsplit-new , :vnew | Open a scratch buffer in a vertical split. |
:hsplit , :hs , :sp | Open the file in a horizontal split. |
:hsplit-new , :hnew | Open a scratch buffer in a horizontal split. |
:tutor | Open the tutorial. |
:goto , :g | Go to line number. |
:set-language , :lang | Set the language of current buffer. |
:set-option , :set | Set a config option at runtime. |
:get-option , :get | Get the current value of a config option. |
:sort | Sort ranges in selection. |
:rsort | Sort ranges in selection in reverse order. |
:tree-sitter-subtree , :ts-subtree | Display tree sitter subtree under cursor, primarily for debugging queries. |
:config-reload | Refreshes helix's config. |
:config-open | Open the helix config.toml file. |
:pipe | Pipe each selection to the 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.
Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
---|---|---|---|---|
bash | ✓ | bash-language-server | ||
c | ✓ | ✓ | ✓ | clangd |
c-sharp | ✓ | OmniSharp | ||
cmake | ✓ | ✓ | ✓ | cmake-language-server |
comment | ✓ | |||
cpp | ✓ | ✓ | ✓ | clangd |
css | ✓ | vscode-css-language-server | ||
dart | ✓ | ✓ | dart | |
devicetree | ✓ | ✓ | ||
dockerfile | ✓ | docker-langserver | ||
eex | ✓ | |||
ejs | ✓ | |||
elixir | ✓ | elixir-ls | ||
elm | ✓ | elm-language-server | ||
erb | ✓ | |||
erlang | ✓ | erlang_ls | ||
fish | ✓ | ✓ | ✓ | |
gdscript | ✓ | ✓ | ||
git-commit | ✓ | |||
git-config | ✓ | |||
git-diff | ✓ | |||
git-rebase | ✓ | |||
gleam | ✓ | |||
glsl | ✓ | ✓ | ||
go | ✓ | ✓ | ✓ | gopls |
gomod | ✓ | gopls | ||
gowork | ✓ | gopls | ||
graphql | ✓ | |||
hare | ✓ | ✓ | ||
haskell | ✓ | haskell-language-server-wrapper | ||
hcl | ✓ | ✓ | terraform-ls | |
heex | ✓ | |||
html | ✓ | vscode-html-language-server | ||
iex | ✓ | |||
java | ✓ | |||
javascript | ✓ | ✓ | typescript-language-server | |
json | ✓ | ✓ | vscode-json-language-server | |
jsx | ✓ | ✓ | typescript-language-server | |
julia | ✓ | julia | ||
kotlin | ✓ | kotlin-language-server | ||
latex | ✓ | texlab | ||
lean | ✓ | lean | ||
ledger | ✓ | |||
llvm | ✓ | ✓ | ✓ | |
llvm-mir | ✓ | ✓ | ✓ | |
llvm-mir-yaml | ✓ | ✓ | ||
lua | ✓ | ✓ | ||
make | ✓ | |||
markdown | ✓ | |||
mint | mint | |||
nickel | ✓ | ✓ | nls | |
nix | ✓ | ✓ | rnix-lsp | |
nu | ✓ | |||
ocaml | ✓ | ✓ | ocamllsp | |
ocaml-interface | ✓ | ocamllsp | ||
org | ✓ | |||
perl | ✓ | ✓ | ✓ | |
php | ✓ | ✓ | ✓ | intelephense |
prolog | swipl | |||
protobuf | ✓ | ✓ | ||
python | ✓ | ✓ | ✓ | pylsp |
r | ✓ | R | ||
racket | racket | |||
regex | ✓ | |||
rescript | ✓ | ✓ | rescript-language-server | |
rmarkdown | ✓ | ✓ | R | |
ron | ✓ | ✓ | ||
ruby | ✓ | ✓ | ✓ | solargraph |
rust | ✓ | ✓ | ✓ | rust-analyzer |
scala | ✓ | ✓ | metals | |
solidity | ✓ | solc | ||
sql | ✓ | |||
svelte | ✓ | ✓ | svelteserver | |
swift | ✓ | sourcekit-lsp | ||
tablegen | ✓ | ✓ | ✓ | |
tfvars | terraform-ls | |||
toml | ✓ | taplo | ||
tsq | ✓ | |||
tsx | ✓ | typescript-language-server | ||
twig | ✓ | |||
typescript | ✓ | ✓ | typescript-language-server | |
vala | ✓ | vala-language-server | ||
vue | ✓ | vls | ||
wgsl | ✓ | |||
yaml | ✓ | ✓ | yaml-language-server | |
zig | ✓ | ✓ | zls |
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
Configuration
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"
[editor]
line-number = "relative"
mouse = false
[editor.cursor-shape]
insert = "bar"
normal = "block"
select = "underline"
[editor.file-picker]
hidden = false
Editor
[editor]
Section
Key | Description | Default |
---|---|---|
scrolloff | Number of lines of padding around the edge of the screen when scrolling. | 3 |
mouse | Enable mouse mode. | true |
middle-click-paste | Middle click paste support. | true |
scroll-lines | Number of lines to scroll per scroll wheel step. | 3 |
shell | Shell to use when running external commands. | Unix: ["sh", "-c"] Windows: ["cmd", "/C"] |
line-number | Line 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 |
gutters | Gutters to display: Available are diagnostics and line-numbers , note that diagnostics also includes other features like breakpoints | ["diagnostics", "line-numbers"] |
auto-completion | Enable automatic pop up of auto-completion. | true |
idle-timeout | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | 400 |
completion-trigger-len | The min-length of word under cursor to trigger autocompletion | 2 |
auto-info | Whether to display infoboxes | true |
true-color | Set to true to override automatic detection of terminal truecolor support in the event of a false negative. | false |
rulers | List of column positions at which to display the rulers. Can be overridden by language specific rulers in languages.toml file. | [] |
[editor.lsp]
Section
Key | Description | Default |
---|---|---|
display-messages | Display LSP progress messages below statusline1 | false |
A progress spinner is always 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 none
.
Key | Description | Default |
---|---|---|
normal | Cursor shape in normal mode | block |
insert | Cursor shape in insert mode | block |
select | Cursor shape in select mode | block |
[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.
Key | Description | Default |
---|---|---|
hidden | Enables ignoring hidden files. | true |
parents | Enables reading ignore files from parent directories. | true |
ignore | Enables reading .ignore files. | true |
git-ignore | Enables reading .gitignore files. | true |
git-global | Enables reading global .gitignore, whose path is specified in git's config: core.excludefile option. | true |
git-exclude | Enables reading .git/info/exclude files. | true |
max-depth | Set with an integer value for maximum depth to recurse. | Defaults to None . |
[editor.auto-pairs]
Section
Enable automatic insertion of pairs to parentheses, brackets, etc. Can be a simple boolean value, or a specific mapping of pairs of single characters.
Key | Description |
---|---|
false | Completely disable auto pairing, regardless of language-specific settings |
true | Use the default pairs: (){}[]''""`` |
Mapping of pairs | e.g. { "(" = ")", "{" = "}", ... } |
Example
[editor.auto-pairs]
'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
'<' = '>'
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 ''
[[language]]
name = "rust"
[language.auto-pairs]
'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
'<' = '>'
[editor.search]
Section
Search specific options.
Key | Description | Default |
---|---|---|
smart-case | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | true |
wrap-around | Whether the search should wrap after depleting the matches | true |
[editor.whitespace]
Section
Options for rendering whitespace with visible characters. Use :set whitespace.render all
to temporarily enable visible whitespace.
Key | Description | Default |
---|---|---|
render | Whether to render whitespace. May either be "all" or "none" , or a table with sub-keys space , tab , and newline . | "none" |
characters | Literal characters to use when rendering whitespace. Sub-keys may be any of tab , space or newline | See example below |
Example
[editor.whitespace]
render = "all"
# or control each character
[editor.whitespace.render]
space = "all"
tab = "all"
newline = "none"
[editor.whitespace.characters]
space = "·"
tab = "→"
newline = "⏎"
Themes
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"
[palette]
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 |
---|
black |
red |
green |
yellow |
blue |
magenta |
cyan |
gray |
light-red |
light-green |
light-yellow |
light-blue |
light-magenta |
light-cyan |
light-gray |
white |
Modifiers
The following values may be used as modifiers.
Less common modifiers might not be supported by your terminal emulator.
Modifier |
---|
bold |
dim |
italic |
underlined |
slow_blink |
rapid_blink |
reversed |
hidden |
crossed_out |
Scopes
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
- Typesbuiltin
- 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 expressionsspecial
path
url
symbol
- Erlang/Elixir atoms, Ruby symbols, Clojure keywords
-
comment
- Code commentsline
- Single line comments (//
)block
- Block comments (e.g. (/* */
)documentation
- Documentation comments (e.g.///
in Rust)
-
variable
- Variablesbuiltin
- Reserved language variables (self
,this
,super
, etc)parameter
- Function parametersother
member
- Fields of composite data types (e.g. structs, unions)
function
(TODO: ?)
-
label
-
punctuation
delimiter
- Commas, colonsbracket
- Parentheses, angle brackets, etc.
-
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
-
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 linkslabel
- non-url link referencestext
- url and image descriptions in links
quote
raw
inline
block
-
diff
- version control changesplus
- additionsminus
- deletionsdelta
- modificationsmoved
- renamed or moved files/changes
Interface
These scopes are used for theming the editor interface.
markup
normal
completion
- for completion doc popup uihover
- for hover popup ui
heading
completion
- for completion doc popup uihover
- for hover popup ui
raw
inline
completion
- for completion doc popup uihover
- for hover popup ui
Key | Notes |
---|---|
ui.background | |
ui.cursor | |
ui.cursor.insert | |
ui.cursor.select | |
ui.cursor.match | Matching bracket etc. |
ui.cursor.primary | Cursor with primary selection |
ui.linenr | Line numbers |
ui.linenr.selected | Line number for the line the cursor is on |
ui.statusline | Statusline |
ui.statusline.inactive | Statusline (unfocused document) |
ui.popup | Documentation popups (e.g space-k) |
ui.popup.info | Prompt for multiple key options |
ui.window | Border lines separating splits |
ui.help | Description box for commands |
ui.text | Command prompts, popup text, etc. |
ui.text.focus | |
ui.text.info | The key: command text in ui.popup.info boxes |
ui.virtual.ruler | Ruler columns (see the editor.rulers config) |
ui.virtual.whitespace | Visible white-space characters |
ui.menu | Code and command completion menus |
ui.menu.selected | Selected autocomplete item |
ui.selection | For selections in the editing area |
ui.selection.primary | |
warning | Diagnostics warning (gutter) |
error | Diagnostics error (gutter) |
info | Diagnostics info (gutter) |
hint | Diagnostics hint (gutter) |
diagnostic | For text in editing area |
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'
[keys.normal]
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
[keys.insert]
"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 name | Representation |
---|---|
Backspace | "backspace" |
Space | "space" |
Return/Enter | "ret" |
< | "lt" |
> | "gt" |
+ | "plus" |
- | "minus" |
; | "semicolon" |
% | "percent" |
Left | "left" |
Right | "right" |
Up | "up" |
Down | "down" |
Home | "home" |
End | "end" |
Page Up | "pageup" |
Page Down | "pagedown" |
Tab | "tab" |
Delete | "del" |
Insert | "ins" |
Null | "null" |
Escape | "esc" |
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 ofstatic_commands!
macro and theTypableCommandList
.
Hooks
Languages
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.
A local languages.toml
can be created within a .helix
directory. Its settings will be merged with both the global and default configs.
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
[[language]]
name = "rust"
auto-format = false
Tree-sitter grammars
Tree-sitter grammars can also be configured in languages.toml
:
# in <config_dir>/helix/languages.toml
[[grammar]]
name = "rust"
source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }
[[grammar]]
name = "c"
source = { path = "/path/to/tree-sitter-c" }
You may use a top-level use-grammars
key to control which grammars are fetched and built.
# 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.
Guides
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
found in the root of the repository;
this languages.toml
file is included at compilation time, and is
distinct from the languages.toml
file in the user's configuration
directory.
[[language]]
name = "mylang"
scope = "scope.mylang"
injection-regex = "^mylang$"
file-types = ["mylang", "myl"]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
language-server = { command = "mylang-lsp", args = ["--stdio"] }
These are the available keys and descriptions for the file.
Key | Description |
---|---|
name | The name of the language |
scope | A 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-regex | regex 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-types | The filetypes of the language, for example ["yml", "yaml"] . Extensions and full file names are supported. |
shebangs | The interpreters from the shebang line, for example ["sh", "bash"] |
roots | A set of marker files to look for when trying to find the workspace root. For example Cargo.lock , yarn.lock |
auto-format | Whether to autoformat this language when saving |
diagnostic-severity | Minimal severity of diagnostic for it to be displayed. (Allowed values: Error , Warning , Info , Hint ) |
comment-token | The token to use as a comment-token |
indent | The indent to use. Has sub keys tab-width and unit |
language-server | The Language Server to run. Has sub keys command and args |
config | Language Server configuration |
grammar | The tree-sitter grammar to use (defaults to the value of name ) |
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
.
[[grammar]]
name = "mylang"
source = { git = "https://github.com/example/mylang", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }
Grammar configuration takes these keys:
Key | Description |
---|---|
name | The name of the tree-sitter grammar |
source | The 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:
Key | Description |
---|---|
git | A git remote URL from which the grammar should be cloned |
rev | The revision (commit hash or tag) which should be fetched |
subpath | A 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 |
Or a path
key with an absolute path to a locally available grammar directory.
Queries
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 andhx --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 |
---|
function.inside |
function.around |
class.inside |
class.around |
parameter.inside |
comment.inside |
comment.around |
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.
Scopes
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 fromtail
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:
(assignment_expression
right: (_) @indent
(#set! "scope" "all"))
Capture Types
-
@indent
(default scopetail
): 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 scopeall
): Decrease the indent level by 1. The same rules as for@indent
apply.
Predicates
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:
(some_kind
(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.