vimtricks.wiki Concise Vim tricks, one at a time.

How do I use LSP to create accurate code folds based on the language server's understanding of the document structure?

Answer

vim.lsp.foldexpr()

Explanation

Neovim 0.11 introduced vim.lsp.foldexpr(), which uses the LSP textDocument/foldingRange request to determine fold boundaries. Unlike foldmethod=indent (indentation-based) or the Treesitter equivalent, LSP folding leverages the language server's semantic understanding of the code — giving accurate folds even for languages without Treesitter parsers.

How it works

Set foldmethod=expr and point foldexpr at vim.lsp.foldexpr(). Neovim queries the active language server for folding ranges and uses them as fold boundaries.

-- In init.lua (or an LspAttach autocmd for buffer-local setup):
vim.opt.foldmethod = 'expr'
vim.opt.foldexpr = 'v:lua.vim.lsp.foldexpr()'
vim.opt.foldlevel = 99
vim.opt.foldlevelstart = 99

For buffer-local setup (recommended, applies only when an LSP is attached):

vim.api.nvim_create_autocmd('LspAttach', {
  callback = function(args)
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    if client and client.supports_method('textDocument/foldingRange') then
      vim.wo.foldmethod = 'expr'
      vim.wo.foldexpr = 'v:lua.vim.lsp.foldexpr()'
    end
  end,
})

Tips

  • Requires a language server that supports textDocument/foldingRange — most modern servers do (rust-analyzer, pyright, ts-language-server, etc.)
  • Complements vim.treesitter.foldexpr(): use LSP folding for semantic regions (imports block, class body) and Treesitter for syntactic ones
  • Requires Neovim 0.11 or later
  • foldlevel = 99 starts the session with all folds open

Next

What is the difference between the inner word (iw) and inner WORD (iW) text objects in Vim?