neovim/runtime/lua/vim/highlight.lua

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

152 lines
4.8 KiB
Lua
Raw Normal View History

---@defgroup vim.highlight
---
2023-09-14 06:23:01 -07:00
--- Nvim includes a function for highlighting a selection on yank.
---
--- To enable it, add the following to your `init.vim`:
---
--- ```vim
--- au TextYankPost * silent! lua vim.highlight.on_yank()
--- ```
---
--- You can customize the highlight group and the duration of the highlight via:
---
--- ```vim
--- au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150}
--- ```
---
--- If you want to exclude visual selections from highlighting on yank, use:
---
--- ```vim
--- au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false}
--- ```
local api = vim.api
local M = {}
--- Table with default priorities used for highlighting:
--- - `syntax`: `50`, used for standard syntax highlighting
--- - `treesitter`: `100`, used for treesitter-based highlighting
--- - `semantic_tokens`: `125`, used for LSP semantic token highlighting
--- - `diagnostics`: `150`, used for code analysis such as diagnostics
--- - `user`: `200`, used for user-triggered highlights such as LSP document
--- symbols or `on_yank` autocommands
M.priorities = {
syntax = 50,
treesitter = 100,
semantic_tokens = 125,
diagnostics = 150,
user = 200,
}
--- Apply highlight group to range of text.
2020-05-31 11:56:00 -07:00
---
---@param bufnr integer Buffer number to apply highlighting to
---@param ns integer Namespace to add highlight to
---@param higroup string Highlight group to use for highlighting
---@param start integer[]|string Start of region as a (line, column) tuple or string accepted by |getpos()|
---@param finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()|
---@param opts table|nil Optional parameters
--- - regtype type of range (see |setreg()|, default charwise)
--- - inclusive boolean indicating whether the range is end-inclusive (default false)
--- - priority number indicating priority of highlight (default priorities.user)
function M.range(bufnr, ns, higroup, start, finish, opts)
opts = opts or {}
local regtype = opts.regtype or 'v'
local inclusive = opts.inclusive or false
local priority = opts.priority or M.priorities.user
local scoped = opts._scoped or false
2020-05-31 11:56:00 -07:00
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
-- TODO: in case of 'v', 'V' (not block), this should calculate equivalent
-- bounds (row, col, end_row, end_col) as multiline regions are natively
-- supported now
local region = vim.region(bufnr, start, finish, regtype, inclusive)
2020-05-31 11:56:00 -07:00
for linenr, cols in pairs(region) do
local end_row
if cols[2] == -1 then
end_row = linenr + 1
cols[2] = 0
end
api.nvim_buf_set_extmark(bufnr, ns, linenr, cols[1], {
hl_group = higroup,
end_row = end_row,
end_col = cols[2],
priority = priority,
strict = false,
scoped = scoped,
})
2020-05-31 11:56:00 -07:00
end
end
local yank_ns = api.nvim_create_namespace('hlyank')
local yank_timer
local yank_cancel
--- Highlight the yanked text
2020-05-31 11:56:00 -07:00
---
--- @param opts table|nil Optional parameters
--- - higroup highlight group for yanked region (default "IncSearch")
--- - timeout time in ms before highlight is cleared (default 150)
--- - on_macro highlight when executing macro (default false)
--- - on_visual highlight when yanking visual selection (default true)
--- - event event structure (default vim.v.event)
--- - priority integer priority (default |vim.highlight.priorities|`.user`)
function M.on_yank(opts)
vim.validate({
opts = {
opts,
function(t)
if t == nil then
return true
else
return type(t) == 'table'
end
end,
'a table or nil to configure options (see `:h highlight.on_yank`)',
},
})
opts = opts or {}
local event = opts.event or vim.v.event
local on_macro = opts.on_macro or false
local on_visual = (opts.on_visual ~= false)
if not on_macro and vim.fn.reg_executing() ~= '' then
return
end
if event.operator ~= 'y' or event.regtype == '' then
return
end
if not on_visual and event.visual then
return
end
local higroup = opts.higroup or 'IncSearch'
local timeout = opts.timeout or 150
local bufnr = vim.api.nvim_get_current_buf()
local winid = vim.api.nvim_get_current_win()
if yank_timer then
yank_timer:close()
yank_cancel()
end
M.range(bufnr, yank_ns, higroup, "'[", "']", {
regtype = event.regtype,
inclusive = event.inclusive,
priority = opts.priority or M.priorities.user,
_scoped = true,
})
vim.api.nvim_win_add_ns(winid, yank_ns)
yank_cancel = function()
yank_timer = nil
yank_cancel = nil
pcall(vim.api.nvim_buf_clear_namespace, bufnr, yank_ns, 0, -1)
pcall(vim.api.nvim_win_remove_ns, winid, yank_ns)
end
yank_timer = vim.defer_fn(yank_cancel, timeout)
end
return M