Table of Contents
Open Table of Contents
What is bro yapping about?
I love Neovim, the switch from VSCode to neovim has been gratifying for many reasons. A big one being rediscovering joy in putting time into thing and thing rewarding you with being speedy (no citations here trust me bro I am faster), more things to learn and knowledge.
What am I talking about? How about discovering more about language servers, abstract syntax trees and lower level understanding of how editors works - the concept of buffers and windows and “tabs” (tabs != tabs as you will come to realise).
While I’d like to believe that vim makes me faster, the main reason I think it did for me was to enjoy and not fear the terminal. It’s not as if devs graduate out of university fearing the terminal but it’s more that they didn’t take enough time to familiarise it and learn it beyond git clone, commit, push.
In the spirit of learning and enjoying tools and terminals - I will be discussing Helix today.
And then there was Helix
Why did I learn helix? My baby nvim config repo has matured into a fine 846 commits boy already. So what was the issue? The main reason was portability - and it stemmed directly from my previous #complain article about how I hate setting up new Linux machines.
Neovim is chunky. It is chunky because I fed it 86 plugins as of this moment. Some plugins require binaries beyond neovim. Some require other esoteric setup I already forgot about. Mostly I just tire of having to move about plugins, update them, solve new config issues. Just to get it working on remote machines or new system when I just want to do quick fixes. Or maybe there was not enough horse power to even run it (See $1/month VPS).
Helix is a new kid on the block. It offers familiar yet different philosophy of the motion verb grammar we’re all used to. It took me a good few days to get used to it and then to get used to AND discover how amazing multicursor first editing is. Man I do not miss typing clunky :s//g
statement when I want to quickly replace X things in a file.
Best of all, it doesn’t have big dependencies to lug around apart from your config.toml
- but you’d have to do that anyway if you are a CLI binary fiend.
What I like about Helix binds
Basics
Indentation
Literally just >
once in normal mode to indent stuff.
keymap("n", ">", function()
local count = vim.v.count1 -- Gets the count (default 1)
for _ = 1, count do
vim.cmd('normal! >>')
end
end)
keymap("n", "<", function()
local count = vim.v.count1
for _ = 1, count do
vim.cmd('normal! <<')
end
end)
Jump to word
The keybind gw
is something I never realised I wanted. I was always awkwardly using <C-space><C-space>
to hop to word with labels given in the visible screen. flash.nvim
and hop.nvim
users will know what I am talking about. I never found the right bind for it but [g]o to [w]ord
makes so much sense that I feel silly for not doing that. I mean, did you even know that gw
and gq
in neovim does formatting?
:help gw :help gq gh/hop.nvim gh/flash.nvim
keymap({ "n" }, "gw", flash_util.flash_word)
Go to start/end of line
Any one of you a big fan of $
? Me neither. What about 0
to go to start of line? Oh but you know that gj
and gk
does soft-wrap aware vertical movement right? So why does gh
and gl
doesn’t also move to start/end of line?? This is the default behaviour in Helix that I enjoyed.
keymap({ "n", "v", "x" }, "gh", "0")
keymap({ "n", "v", "x" }, "gl", "$")
The multicursor elephant in the room
This is honestly the main selling point of Helix, first class multicursors. Yes you can Ctrl+D just like in VSCode but with bells and whistles. And of course, a different approach. If I hadn’t mentioned it yet, helix is a select-first verb later approach. To give you a quick example the “normal” mode in helix is basically a sliding window visual mode. See this sentence:
AI will not take my heckin job
If your cursor was at the start of the sentence and you press “w” (for [w]ord) multiple times, you would hover over the first word from start/end then the next word’s start/end. It will not encompass the second word from the start unless you initiate “selection” mode.
Ok this is getting confusing with normal=visual and visual=select mode. Let me summarise.
Select the text THEN decide what you do - Helix
Delete the next 2 words - Vim
This builds on to what I am trying to talk about - multicursor. In helix, you can initiate multicursor mode much like VSCode by entering select mode, highlight over the block and press *
to create a cursor at next matching word. Ok nothing fancy there. To build on this, when you highlight over the chunk of text you can also perform 2 operations for more nuance multicursor. Select and Split (not to be confused with select mode).
See this example:
Ok you're right, maybe ChatGPT will take my job. ChatGPT has been getting better,
although ChatGPT latest version is a little questionable and the public facing model
(behind subscription paywall) seems to degrade over time. It has gotten stupider since
I've used it. But man, I'm still going to pay for ChatGPT.
The vim-idiomatic way would be to :s/ChatGPT/MyFavouriteSlopLLM/g
and be done with it. But in Helix you can highlight the range and press s
to directly select ChatGPT in the block, see visually all the multicursors added and real time word change. It actually makes so much sense that it blew me away. The split mode would be if I needed to reorder stuff by using the existing multicursor model and rotate through the content between them. See this example:
3,4,1,2
I would do this xS,))
which expands to
- extend_line
- split_selection
- rotate_selection_contents_forward * 2
These are extremely basic examples but it should be enough to give you a brief example before I move on to telling you how to put this into neovim.
The one and only multicursor.nvim
The plugin basically can do everything I described in helix, their multicursor is best I’ve used and I never explored its featureset until I tried helix. Here’s my config
set({ "n", "x" }, "ga", mc.addCursorOperator, { desc = "[MULTC] Operator" })
set({ "v", "x" }, "<leader>s", mc.splitCursors, { desc = "[MULTC] Split Regex" })
set({ "n", "x" }, "<C-up>", function() mc.lineAddCursor(-1) end, { desc = "[MULTC] Add Cursor Up" })
set({ "n", "x" }, "<C-down>", function() mc.lineAddCursor(1) end, { desc = "[MULTC] Add Cursor Down" })
set({ "n", "x" }, "<leader>/", mc.searchAllAddCursors, { desc = "[MULTC] All Search Matches" })
set({ "v", "x" }, "s", mc.matchCursors, { desc = "[MULTC] All Search Matches" })
set("n", "gV", mc.restoreCursors, { desc = "[MULTC] Restore Cursors" })
set({ "n", "x" }, "<c-q>", mc.toggleCursor, { desc = "[MULTC] Manual" })
-- When "multicursor exists" mode
mc.addKeymapLayer(function(layerSet)
layerSet({ "n", "x" }, "Q", function() mc.matchSkipCursor(-1) end, { desc = "[MULTC] Skip Prev" })
layerSet({ "n", "x" }, "q", function() mc.matchSkipCursor(1) end, { desc = "[MULTC] Skip Next" })
layerSet({ "n", "x" }, "<left>", mc.prevCursor, { desc = "[MULTC] Prev" })
layerSet({ "n", "x" }, "<right>", mc.nextCursor, { desc = "[MULTC] Next" })
layerSet({ "n", "x" }, "<BS>", mc.deleteCursor, { desc = "[MULTC] Delete Cursor" })
layerSet("n", "<CR>", mc.enableCursors, { desc = "[MULTC] Confirm" })
layerSet("n", "<esc>", mc.clearCursors, { desc = "[MULTC] Clear" })
layerSet("n", ",", mc.clearCursors, { desc = "[MULTC] Clear" })
layerSet({ "v", "x" }, "(", function() mc.transposeCursors(-1) end, { desc = "[MULTC] Rotate content left" })
layerSet({ "v", "x" }, ")", function() mc.transposeCursors(1) end, { desc = "[MULTC] Rotate content right" })
layerSet({ "v", "x" }, "&", mc.alignCursors, { desc = "[MULTC] Align contents" })
end)
What I wish Vim had
Helix has columns in their pickers. Vim bros… they can filter based on the columns. This is actually so good you don’t understand. When you use pickers in neovim you basically had to fuzzy over every detail, see these examples:
This makes an insane amount of sense. Imagine doing AST symbol picker over the entire workspace and select test function with columns where the path may contain the word “graph”. You would basically use ripgrep for this instead. This works, first-class in helix. Insane work guys, I really love it.
This sounds very possible as well of course for vim as a plugin, but would you do this to be telescope
compatible? or snacks
? or perhaps fff
? All??? I won’t be answering these questions myself for now as I am busy but maybe one day when I am decently caffeinated I will give this a shot.
What Helix doesn’t have
Sessions
My workflow for editing is basically z proj kris dev
then nv
for hydrating sessions based on the directory. Neovim does have session based options but iirc you have to save your sessions manually. There are plugins to help with this and it is the exact workflow I’ve described and used for years. There has been direct work done on this for helix already which I am planning to add to my maintained fork. I hope to one day see this merged to take one thing potential merge conflict off my fork list. Maybe one day when the plugins PR get merged I won’t have to worry about self-rolling a fork anymore.
Quickfix list
Honestly, I didn’t know how good I had it until I don’t. Quickfix list is GOATED. The ability to locate all the issues and context switch directly to the problem location or remotely solved the problems is something surgeons wished they had. That would be a godlike surgical power. I love quickfix, I love dumping pickers to quickfix and I love remotely fixing things in the quickfix list.
My final thoughts on using both
To keep this mega brief; try Helix if you like Vim. At least try to understand the multicursor model. It’s a great alternative to doing :s//
all day.
Learning Vim made me rediscover the joy of learning. Learning vim led me to learning helix. Now I love both. This article was written in Helix. All my remote machines have my maintained helix fork binary. My workstation main for serious development will still be neovim - 800 config repos will make it hard for anyone to move on :)
I am thankful for every opensource developers to make these awesome tools I can enjoy shitposting about.
Links
nvim config repo - https://github.com/ktunprasert/nvim
I hate setting up new Linux machines - /posts/i-hate-setting-up-new-linux-machines
github/helix - https://github.com/helix-editor/helix
:help gw - https://neovim.io/doc/user/change.html#gw
:help gq - https://neovim.io/doc/user/change.html#gq
gh/hop.nvim - https://github.com/smoka7/hop.nvim
gh/flash.nvim - https://github.com/folke/flash.nvim
github/multicursor.nvim - https://github.com/jake-stewart/multicursor.nvim
direct work - https://github.com/helix-editor/helix/pull/9143
github/helix/plugins-pr - https://github.com/helix-editor/helix/pull/8675
github/quicker.nvim - https://github.com/stevearc/quicker.nvim