Code Folding with UFO
How to set up folds with syntax highlighting, with folds provided by a language server
A coworker recently turned me on to the magic of code folding. What I hadn’t appreciated until recently is that you can copy folded code. This is a really easy way to select and copy large amounts of code. Since I prefer to start building up abstractions by copying and modifying, and then extracting whatever shared logic remains, this has helped me out quite a bit.
So I wanted to add code folding to my Neovim configuration. What this actually means is adding folds to my Neovim configuration. The keybindings for manipulating folds are part of standard Vim setup (if you’re using “which-key,” you can hit “z” to see all of them) but by default you have to set up the actual folds manually. So to make folding easier I want to configure Neovim to automatically create fold points based on the syntax of the code I’m working with.
Treesitter Configuration
At first I tried to do this with just Treesitter, which can be configured to manage folding pretty simply.
local vim = vim
local opt = vim.opt
opt.foldmethod = "expr"
opt.foldexpr = "nvim_treesitter#foldexpr()"
So what’s this about Ufo?
Unfortunately while this works fine for Lua and Go, this isn’t sufficient to get code folding to work for Elm. I don’t fully understand why — I suspect I don’t actually understand what Treesitter is doing in my config right now — but in researching the problem I discovered kevinhwang91/nvim-ufo.
In addition to successfully providing folds for Elm code, Ufo provides a number of helpful folding features:
Syntax highlighting in folds
Open and close folds with the mouse
Combine fold providers and fallback when the primary provider is not available
“Nicer” fold appearance in general
Ufo Configuration
In addition to the plugin itself, Ufo needs some fold-related options to be set.
vim.o.foldcolumn = '1' -- '0' is not bad
vim.foldlevel = 99
vim.o.foldlevelstart = 99
vim.o.foldenable = true
require('ufo').setup()
On older versions of Neovim you may also need to set some keybindings explicitly, but this doesn’t seem to be necessary for version 0.9.1.
By default Ufo gets its code folds from a language server, and by default language servers don’t provide code folding, so you’ll need to configure the ones you want to use to provide folding. This is where I picked up that trick for configuring all LSPs with a shared set of capabilities.
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.foldingRange = {
dynamicRegistration = false,
lineFoldingOnly = true
}
local language_servers = {'lua_ls', 'elmls', 'gopls'}
for _, ls in ipairs(language_servers) do
require('lspconfig')[ls].setup({
capabilities = capabilities
-- you can add other fields for setting up lsp server in this table
})
end
As always, you can also review my current Neovim configuration to see this all in action. Drop me a line if you try to configure this and it doesn’t work — I’ll update my instructions here if there’s something I haven’t accounted for.
There’s more left to cover on this topic — I’d like at some point to explore why Treesitter wasn’t working, as well as what all these settings actually mean — but that should be enough to get you going for now, so this is where I’m leaving things this week.