1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
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,
}
--- @class vim.highlight.range.Opts
--- @inlinedoc
---
--- Type of range. See [getregtype()]
--- (default: `'v'` i.e. charwise)
--- @field regtype? string
---
--- Indicates whether the range is end-inclusive
--- (default: `false`)
--- @field inclusive? boolean
---
--- Indicates priority of highlight
--- (default: `vim.highlight.priorities.user`)
--- @field priority? integer
--- Apply highlight group to range of text.
---
---@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? vim.highlight.range.Opts
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 v_maxcol = vim.v.maxcol
local pos1 = type(start) == 'string' and vim.fn.getpos(start)
or {
bufnr,
start[1] + 1,
start[2] ~= -1 and start[2] ~= v_maxcol and start[2] + 1 or v_maxcol,
0,
}
local pos2 = type(finish) == 'string' and vim.fn.getpos(finish)
or {
bufnr,
finish[1] + 1,
finish[2] ~= -1 and start[2] ~= v_maxcol and finish[2] + 1 or v_maxcol,
0,
}
local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
pos1[2] = math.min(pos1[2], buf_line_count)
pos2[2] = math.min(pos2[2], buf_line_count)
if pos1[2] <= 0 or pos1[3] <= 0 or pos2[2] <= 0 or pos2[3] <= 0 then
return
end
vim._with({ buf = bufnr }, function()
if pos1[3] ~= v_maxcol then
local max_col1 = vim.fn.col({ pos1[2], '$' })
pos1[3] = math.min(pos1[3], max_col1)
end
if pos2[3] ~= v_maxcol then
local max_col2 = vim.fn.col({ pos2[2], '$' })
pos2[3] = math.min(pos2[3], max_col2)
end
end)
local region = vim.fn.getregionpos(pos1, pos2, {
type = regtype,
exclusive = not inclusive,
eol = true,
})
-- For non-blockwise selection, use a single extmark.
if regtype == 'v' or regtype == 'V' then
region = { { region[1][1], region[#region][2] } }
if
regtype == 'V'
or region[1][2][2] == pos1[2] and pos1[3] == v_maxcol
or region[1][2][2] == pos2[2] and pos2[3] == v_maxcol
then
region[1][2][2] = region[1][2][2] + 1
region[1][2][3] = 0
end
end
for _, res in ipairs(region) do
local start_row = res[1][2] - 1
local start_col = res[1][3] - 1
local end_row = res[2][2] - 1
local end_col = res[2][3]
api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, {
hl_group = higroup,
end_row = end_row,
end_col = end_col,
priority = priority,
strict = false,
})
end
end
local yank_ns = api.nvim_create_namespace('hlyank')
local yank_timer --- @type uv.uv_timer_t?
local yank_cancel --- @type fun()?
--- Highlight the yanked text during a |TextYankPost| event.
---
--- Add the following to your `init.vim`:
---
--- ```vim
--- autocmd TextYankPost * silent! lua vim.highlight.on_yank {higroup='Visual', timeout=300}
--- ```
---
--- @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()
assert(yank_cancel)
yank_cancel()
end
vim.api.nvim__ns_set(yank_ns, { wins = { winid } })
M.range(bufnr, yank_ns, higroup, "'[", "']", {
regtype = event.regtype,
inclusive = event.inclusive,
priority = opts.priority or M.priorities.user,
})
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__ns_set, { wins = {} })
end
yank_timer = vim.defer_fn(yank_cancel, timeout)
end
return M
|