Trying out Helix as my text editor

Helix is a modal editor in the same vein as Vim which means that there are different “modes” where the same key strokes mean different things. The main benefit for a modal editor is that many complex actions can be handled with just the keyboard rather than using the mouse or menus. I’ve been a Vim user for over a decade so I was interested to see what a modal editor that doesn’t come from that lineage would look like. My motivation was the complexity of getting my Neovim config set up just right when working with language servers and tree-sitter. Helix had these features built in so I wanted to see if Helix could improve my productivity without having to mess around with a lot of plugins.

I installed helix using homebrew as recommended (brew install helix). Initally I was confused about the command I needed to run. Turns out that it isn’t Helix but hx. The documentation directed me to use the tutorial with hx --tutor and I’m glad I did as it presented the editing commands in a logical and easy way. I did want to skip ahead at some points as I was familiar with and there are many similarities but as you get further through the differences show up. You’ll want to keep a link to the keymap handy. The are basic similarities in the key bindings but in helix the grammar is often reversed. For example, instead of typing dw you would type wd. This small change is really interesting as you can see what you are selecting (and potentially modify it) before doing anything destructive. It’s sort of like having Vim visual mode always being on. Comands that normally move the cursor in vim will change the selection in helix. So, w won’t just move to the end of the word but instead select the whole word. For basic incantations like dw (in Vim) or wd (in Helix) this will have the same effect - the word will be deleted.

The muscle memory from Vim to Helix can be tough. In particular, I’m fond of typing x to delete a single character and gg (or G) to move to parts of the current file. Having these be different things in Helix took some getting used to. But not in a bad way. I enjoy Helix take on g command to let me move to all parts of the line or file. The mnemonic for g being “go” in Helix versus “global” in Vim seems more natural to me. I do miss marks though. A very common workflow for me was setting locations in Vim using ma or mb to jump around a buffer. In Helix, m is for entering “match mode” that lets you select different parts of the buffer. It’s very useful just hard to break habits. There currently isn’t an equivelent to vim’s marks in helix. Instead you can save locations in a jump list using Crtl + s and then returning to those locations using Space + j and choosing them from a pop up dialog. It’s certainly not as fast so I hope the helix developers consider having named marks in the future.

The other issue I ran into was the default python configuration requires pylsp which doesn’t work well with my setup that uses pyenv. Because I use per-project python environments but pyenv creates a shim in my path which Helix picks up and uses. The language server command needs to be in your path and I couldn’t figure out how to get it to work without installing pylsp into all of my environments. I ended up using pywright which I installed globally through npm. It’s not the cleanest solution but it works.

Installation

brew install helix npm
npm install --location=global pyright

Config

~/.config/helix/config.toml

theme = "noctis"

[editor]
line-number = "absolute"

[editor.cursor-shape]
insert = "bar"
normal = "block"
select = "underline"

[editor.file-picker]
hidden = false

~/.config/helix/languages.toml

[[language]]
name = "python"
scope = "source.python"
injection-regex = "python"
file-types = ["py","pyi","py3","pyw","ptl",".pythonstartup",".pythonrc","SConstruct"]
shebangs = ["python"]
roots = ["setup.py", "setup.cfg", "pyproject.toml"]
comment-token = "#"
language-server = { command = "pyright-langserver", args = ["--stdio"] }
indent = { tab-width = 4, unit = "    " }
# will get "Async jobs timed out" errors if this empty config is not added
config = {}