nvim-ufo
The goal of nvim-ufo is to make Neovim's fold look modern and keep high performance.
ufo-demo.mp4
Table of contents
Features
- Penetrate color for folded lines like other modern editors/IDEs
- Never block Neovim
- Adding folds high accuracy with Folding Range in LSP
- Support fallback and customize strategy for fold provider
- Peek folded line and jump the desired location with less redraw
Quickstart
Requirements
- Neovim 0.6.1 or later
- coc.nvim (optional)
- nvim-treesitter (optional)
Installation
Install with Packer.nvim:
use {'kevinhwang91/nvim-ufo', requires = 'kevinhwang91/promise-async'}
Minimal configuration
use {'kevinhwang91/nvim-ufo', requires = 'kevinhwang91/promise-async'}
vim.o.foldcolumn = '1' -- '0' is not bad
vim.o.foldlevel = 99 -- Using ufo provider need a large value, feel free to decrease the value
vim.o.foldlevelstart = 99
vim.o.foldenable = true
-- Using ufo provider need remap `zR` and `zM`. If Neovim is 0.6.1, remap yourself
vim.keymap.set('n', 'zR', require('ufo').openAllFolds)
vim.keymap.set('n', 'zM', require('ufo').closeAllFolds)
-- Option 1: coc.nvim as LSP client
use {'neoclide/coc.nvim', branch = 'master', run = 'yarn install --frozen-lockfile'}
require('ufo').setup()
--
-- Option 2: nvim lsp as LSP client
-- Tell the server the capability of foldingRange,
-- Neovim hasn't added foldingRange to default capabilities, users must add it manually
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.foldingRange = {
dynamicRegistration = false,
lineFoldingOnly = true
}
local language_servers = require("lspconfig").util.available_servers() -- or list servers manually like {'gopls', 'clangd'}
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
require('ufo').setup()
--
-- Option 3: treesitter as a main provider instead
-- Only depend on `nvim-treesitter/queries/filetype/folds.scm`,
-- performance and stability are better than `foldmethod=nvim_treesitter#foldexpr()`
use {'nvim-treesitter/nvim-treesitter', run = ':TSUpdate'}
require('ufo').setup({
provider_selector = function(bufnr, filetype, buftype)
return {'treesitter', 'indent'}
end
})
--
-- Option 4: disable all providers for all buffers
-- Not recommend, AFAIK, the ufo's providers are the best performance in Neovim
require('ufo').setup({
provider_selector = function(bufnr, filetype, buftype)
return ''
end
})
Usage
Use fold as usual.
Using a provider of ufo, must set a large value for foldlevel
, this is the limitation of
foldmethod=manual
. A small value may close fold automatically if the fold ranges updated.
After running zR
and zM
normal commands will change the foldlevel
, ufo provide the APIs
openAllFolds
/closeAllFolds
to open/close all folds but keep foldlevel
value, need to remap
them.
Like zR
and zM
, if you used zr
and zm
before, please use closeFoldsWith
API to close folds
like set foldlevel=n
but keep foldlevel
value.
Documentation
How does nvim-ufo get the folds?
If ufo detect foldmethod
option is not diff
or marker
, it will request the providers to get
the folds, the request strategy is formed by the main and the fallback. The default value of main is
lsp
and the default value of fallback is indent
which implemented by ufo.
For example, Changing the text in a buffer will request the providers for folds.
foldmethod
option will finally becomemanual
if ufo is working.
Setup and description
{
open_fold_hl_timeout = {
description = [[Time in millisecond between the range to be highlgihted and to be cleared
while opening the folded line, `0` value will disable the highlight]],
default = 400
},
provider_selector = {
description = [[A function as a selector for fold providers. For now, there are
'lsp' and 'treesitter' as main provider, 'indent' as fallback provider]],
default = nil
},
close_fold_kinds = {
description = [[After the buffer is displayed (opened for the first time), close the
folds whose range with `kind` field is included in this option. For now,
'lsp' provider's standardized kinds are 'comment', 'imports' and 'region',
run `UfoInspect` for details if your provider has extended the kinds.]],
default = {}
},
fold_virt_text_handler = {
description = [[A function customize fold virt text, see ### Customize fold text]],
default = nil
},
enable_get_fold_virt_text = {
description = [[Enable a function with `lnum` as a parameter to capture the virtual text
for the folded lines and export the function to `get_fold_virt_text` field of
ctx table as 6th parameter in `fold_virt_text_handler`]],
default = false
},
preview = {
description = [[Configure the options for preview window and remap the keys for current
buffer and preview buffer if the preview window is displayed.
Never worry about the users's keymaps are overridden by ufo, ufo will save
them and restore them if preview window is closed.]],
win_config = {
border = {
description = [[The border for preview window,
`:h nvim_open_win() | call search('border:')`]],
default = 'rounded',
},
winblend = {
description = [[The winblend for preview window, `:h winblend`]],
default = 12,
},
winhighlight = {
description = [[The winhighlight for preview window, `:h winhighlight`]],
default = 'Normal:Normal',
},
maxheight = {
description = [[The max height of preview window]],
default = 20,
}
},
mappings = {
description = [[The table for {function = key}]],
default = [[see ###Preview function table for detail]],
}
}
}
:h ufo
may help you to get the all default configuration.
Preview function table
Function | Action | Def Key |
---|---|---|
scrollB | Type CTRL-B in preview window |
|
scrollF | Type CTRL-F in preview window |
|
scrollU | Type CTRL-U in preview window |
|
scrollD | Type CTRL-D in preview window |
|
scrollE | Type CTRL-E in preview window |
<C-E> |
scrollY | Type CTRL-Y in preview window |
<C-Y> |
jumpTop | Jump to top region in preview window | |
jumpBot | Jump to bottom region in preview window | |
close | In normal window: Close preview window In preview window: Close preview window |
q |
switch | In normal window: Go to preview window In preview window: Go to normal window |
<Tab> |
trace | In normal window: Trace code based on topline In preview window: Trace code based on cursor |
<CR> |
Additional mouse supported:
<ScrollWheelUp>
and<ScrollWheelDown>
: Scroll preview window.<2-LeftMouse>
: Same astrace
action in preview window.
trace
action will open all fold for the folded lines
Commands
Command | Description |
---|---|
UfoEnable | Enable ufo |
UfoDisable | Disable ufo |
UfoInspect | Inspect current buffer information |
UfoAttach | Attach current buffer to enable all features |
UfoDetach | Detach current buffer to disable all features |
UfoEnableFold | Enable to get folds and update them at once for current buffer |
UfoDisableFold | Disable to get folds for current buffer |
API
Highlight groups
" hi default UfoFoldedFg guifg=Normal.foreground
" hi default UfoFoldedBg guibg=Folded.background
hi default link UfoPreviewSbar PmenuSbar
hi default link UfoPreviewThumb PmenuThumb
hi default link UfoPreviewWinBar UfoFoldedBg
hi default link UfoPreviewCursorLine Visual
hi default link UfoFoldedEllipsis Comment
hi default link UfoCursorFoldedLine CursorLine
UfoFoldedFg
: Foreground for raw text of folded line.UfoFoldedBg
: Background of folded line.UfoPreviewSbar
: Scroll bar of preview window, only take effect if the border is missing right horizontal line, likeborder = 'none'
.UfoPreviewCursorLine
: Highlight current line in preview window if it isn't the start of folded lines.UfoPreviewWinBar
: Virtual winBar of preview window.UfoPreviewThumb
: Thumb of preview window.UfoFoldedEllipsis
: Ellipsis at the end of folded line, invalid iffold_virt_text_handler
is set.UfoCursorFoldedLine
: Highlight the folded line under the cursor
Advanced configuration
Configuration can be found at example.lua
Customize configuration
local ftMap = {
vim = 'indent',
python = {'indent'},
git = ''
}
require('ufo').setup({
open_fold_hl_timeout = 150,
close_fold_kinds = {'imports', 'comment'},
preview = {
win_config = {
border = {'', 'β', '', '', '', 'β', '', ''},
winhighlight = 'Normal:Folded',
winblend = 0
},
mappings = {
scrollU = '<C-u>',
scrollD = '<C-d>',
jumpTop = '[',
jumpBot = ']'
}
},
provider_selector = function(bufnr, filetype, buftype)
-- if you prefer treesitter provider rather than lsp,
-- return ftMap[filetype] or {'treesitter', 'indent'}
return ftMap[filetype]
-- refer to ./doc/example.lua for detail
end
})
vim.keymap.set('n', 'zR', require('ufo').openAllFolds)
vim.keymap.set('n', 'zM', require('ufo').closeAllFolds)
vim.keymap.set('n', 'zr', require('ufo').openFoldsExceptKinds)
vim.keymap.set('n', 'zm', require('ufo').closeFoldsWith) -- closeAllFolds == closeFoldsWith(0)
vim.keymap.set('n', 'K', function()
local winid = require('ufo').peekFoldedLinesUnderCursor()
if not winid then
-- choose one of coc.nvim and nvim lsp
vim.fn.CocActionAsync('definitionHover') -- coc.nvim
vim.lsp.buf.hover()
end
end)
Customize fold text
Adding number suffix of folded lines instead of the default ellipsis, here is the example:
local handler = function(virtText, lnum, endLnum, width, truncate)
local newVirtText = {}
local suffix = (' σ° %d '):format(endLnum - lnum)
local sufWidth = vim.fn.strdisplaywidth(suffix)
local targetWidth = width - sufWidth
local curWidth = 0
for _, chunk in ipairs(virtText) do
local chunkText = chunk[1]
local chunkWidth = vim.fn.strdisplaywidth(chunkText)
if targetWidth > curWidth + chunkWidth then
table.insert(newVirtText, chunk)
else
chunkText = truncate(chunkText, targetWidth - curWidth)
local hlGroup = chunk[2]
table.insert(newVirtText, {chunkText, hlGroup})
chunkWidth = vim.fn.strdisplaywidth(chunkText)
-- str width returned from truncate() may less than 2nd argument, need padding
if curWidth + chunkWidth < targetWidth then
suffix = suffix .. (' '):rep(targetWidth - curWidth - chunkWidth)
end
break
end
curWidth = curWidth + chunkWidth
end
table.insert(newVirtText, {suffix, 'MoreMsg'})
return newVirtText
end
-- global handler
-- `handler` is the 2nd parameter of `setFoldVirtTextHandler`,
-- check out `./lua/ufo.lua` and search `setFoldVirtTextHandler` for detail.
require('ufo').setup({
fold_virt_text_handler = handler
})
-- buffer scope handler
-- will override global handler if it is existed
-- local bufnr = vim.api.nvim_get_current_buf()
-- require('ufo').setFoldVirtTextHandler(bufnr, handler)
Feedback
- If you get an issue or come up with an awesome idea, don't hesitate to open an issue in github.
- If you think this plugin is useful or cool, consider rewarding it a star.
License
The project is licensed under a BSD-3-clause license. See LICENSE file for details.