From 9aa4608401384a59ea54e0ba800ce063775e8ae9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 20 May 2026 17:33:01 -0400 Subject: [PATCH] refactor: introduce nvim_on internally #39883 Problem: `nvim_create_autocmd` is too verbose and its `callback` requires extra "nesting". Solution: Introduce `nvim_on`. Start using it internally. Then we can get a feel for how it should look before making it public. --- runtime/lua/editorconfig.lua | 29 +- runtime/lua/nvim/matchparen.lua | 49 +-- runtime/lua/vim/_core/defaults.lua | 336 +++++++++---------- runtime/lua/vim/_core/ui2.lua | 37 +- runtime/lua/vim/_core/ui2/messages.lua | 55 ++- runtime/lua/vim/_core/util.lua | 29 ++ runtime/lua/vim/diagnostic.lua | 15 +- runtime/lua/vim/diagnostic/_display.lua | 11 +- runtime/lua/vim/diagnostic/_handlers.lua | 39 +-- runtime/lua/vim/diagnostic/_store.lua | 20 +- runtime/lua/vim/health/health.lua | 44 ++- runtime/lua/vim/lsp.lua | 120 +++---- runtime/lua/vim/lsp/_folding_range.lua | 59 ++-- runtime/lua/vim/lsp/buf.lua | 23 +- runtime/lua/vim/lsp/completion.lua | 135 ++++---- runtime/lua/vim/lsp/diagnostic.lua | 51 ++- runtime/lua/vim/lsp/document_color.lua | 23 +- runtime/lua/vim/lsp/inlay_hint.lua | 84 +++-- runtime/lua/vim/lsp/inline_completion.lua | 21 +- runtime/lua/vim/lsp/linked_editing_range.lua | 76 ++--- runtime/lua/vim/lsp/on_type_formatting.lua | 46 ++- runtime/lua/vim/lsp/semantic_tokens.lua | 21 +- runtime/lua/vim/lsp/util.lua | 44 ++- runtime/lua/vim/pack.lua | 5 +- runtime/lua/vim/snippet.lua | 144 ++++---- runtime/lua/vim/treesitter/_fold.lua | 63 ++-- runtime/lua/vim/treesitter/dev.lua | 237 ++++++------- runtime/lua/vim/treesitter/query.lua | 16 +- runtime/lua/vim/tty.lua | 13 +- runtime/lua/vim/ui.lua | 16 +- runtime/lua/vim/ui/img.lua | 10 +- test/functional/core/startup_spec.lua | 11 +- 32 files changed, 842 insertions(+), 1040 deletions(-) diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index 472874c1a9..537a5f1a44 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -57,6 +57,8 @@ --- --- The following properties are supported by default: +local nvim_on = require('vim._core.util').nvim_on + --- @type table local properties = {} @@ -163,16 +165,12 @@ function properties.trim_trailing_whitespace(bufnr, val) 'trim_trailing_whitespace must be either "true" or "false"' ) if val == 'true' then - vim.api.nvim_create_autocmd('BufWritePre', { - group = 'nvim.editorconfig', - buf = bufnr, - callback = function() - local view = vim.fn.winsaveview() - vim.api.nvim_command('silent! undojoin') - vim.api.nvim_command('silent keepjumps keeppatterns %s/\\s\\+$//e') - vim.fn.winrestview(view) - end, - }) + nvim_on('BufWritePre', 'nvim.editorconfig', { buf = bufnr }, function() + local view = vim.fn.winsaveview() + vim.api.nvim_command('silent! undojoin') + vim.api.nvim_command('silent keepjumps keeppatterns %s/\\s\\+$//e') + vim.fn.winrestview(view) + end) else vim.api.nvim_clear_autocmds({ event = 'BufWritePre', @@ -192,14 +190,9 @@ function properties.insert_final_newline(bufnr, val) -- so only change 'endofline' right before writing the file local endofline = val == 'true' if vim.bo[bufnr].endofline ~= endofline then - vim.api.nvim_create_autocmd('BufWritePre', { - group = 'nvim.editorconfig', - buf = bufnr, - once = true, - callback = function() - vim.bo[bufnr].endofline = endofline - end, - }) + nvim_on('BufWritePre', 'nvim.editorconfig', { buf = bufnr, once = true }, function() + vim.bo[bufnr].endofline = endofline + end) end end diff --git a/runtime/lua/nvim/matchparen.lua b/runtime/lua/nvim/matchparen.lua index e3c3d3d716..34c4a83274 100644 --- a/runtime/lua/nvim/matchparen.lua +++ b/runtime/lua/nvim/matchparen.lua @@ -1,5 +1,6 @@ local api = vim.api local fn = vim.fn +local nvim_on = require('vim._core.util').nvim_on local M = {} @@ -42,41 +43,21 @@ function M.enable() local group = api.nvim_create_augroup('matchparen', { clear = true }) -- Replace all matchparen autocommands - api.nvim_create_autocmd({ - 'CursorMoved', - 'CursorMovedI', - 'WinEnter', - 'WinScrolled', - 'TextChanged', - 'TextChangedI', - }, { - group = group, - callback = function() + nvim_on( + { 'CursorMoved', 'CursorMovedI', 'WinEnter', 'WinScrolled', 'TextChanged', 'TextChangedI' }, + group, + function() M.highlight_matching_pair() - end, - }) - api.nvim_create_autocmd('BufWinEnter', { - group = group, - callback = function() - api.nvim_create_autocmd('SafeState', { - group = group, - once = true, - callback = function() - M.highlight_matching_pair() - end, - }) - end, - }) - api.nvim_create_autocmd({ - 'WinLeave', - 'BufLeave', - 'TextChangedP', - }, { - group = group, - callback = function() - M.remove_matches() - end, - }) + end + ) + nvim_on('BufWinEnter', group, function() + nvim_on('SafeState', group, { once = true }, function() + M.highlight_matching_pair() + end) + end) + nvim_on({ 'WinLeave', 'BufLeave', 'TextChangedP' }, group, function() + M.remove_matches() + end) -- Define commands that will disable and enable the plugin. api.nvim_create_user_command('DoMatchParen', function() diff --git a/runtime/lua/vim/_core/defaults.lua b/runtime/lua/vim/_core/defaults.lua index 4490d315af..88f41c82d3 100644 --- a/runtime/lua/vim/_core/defaults.lua +++ b/runtime/lua/vim/_core/defaults.lua @@ -1,5 +1,7 @@ -- Default user-commands, autocmds, mappings, menus. +local nvim_on = require('vim._core.util').nvim_on + --- Default user commands do vim.api.nvim_create_user_command('Inspect', function(cmd) @@ -547,15 +549,13 @@ do end local nvim_popupmenu_augroup = vim.api.nvim_create_augroup('nvim.popupmenu', {}) - vim.api.nvim_create_autocmd('MenuPopup', { + nvim_on('MenuPopup', nvim_popupmenu_augroup, { pattern = '*', - group = nvim_popupmenu_augroup, desc = 'Mouse popup menu', -- nested = true, - callback = function() - enable_ctx_menu() - end, - }) + }, function() + enable_ctx_menu() + end) end --- Default autocommands. See |default-autocmds| @@ -564,12 +564,9 @@ do if vim.v.vim_did_enter then require('vim._core.log').check_log_file() else - vim.api.nvim_create_autocmd('VimEnter', { - once = true, - callback = function() - require('vim._core.log').check_log_file() - end, - }) + nvim_on('VimEnter', nil, { once = true }, function() + require('vim._core.log').check_log_file() + end) end local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim.terminal', {}) @@ -581,21 +578,19 @@ do command = "if !exists('b:term_title')|call jobstart(matchstr(expand(\"\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'term': v:true, 'cwd': expand(get(matchlist(expand(\"\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})", }) - vim.api.nvim_create_autocmd({ 'TermClose' }, { - group = nvim_terminal_augroup, + nvim_on({ 'TermClose' }, nvim_terminal_augroup, { nested = true, desc = 'Automatically close terminal buffers when started with no arguments and exiting without an error', - callback = function(ev) - if vim.v.event.status ~= 0 then - return - end - local info = vim.api.nvim_get_chan_info(vim.bo[ev.buf].channel) - local argv = info.argv or {} - if table.concat(argv, ' ') == vim.o.shell then - vim.api.nvim_buf_delete(ev.buf, { force = true }) - end - end, - }) + }, function(ev) + if vim.v.event.status ~= 0 then + return + end + local info = vim.api.nvim_get_chan_info(vim.bo[ev.buf].channel) + local argv = info.argv or {} + if table.concat(argv, ' ') == vim.o.shell then + vim.api.nvim_buf_delete(ev.buf, { force = true }) + end + end) local nvim_terminal_exitmsg_ns = vim.api.nvim_create_namespace('nvim.terminal.exitmsg') @@ -609,95 +604,82 @@ do }) end - vim.api.nvim_create_autocmd('TermClose', { - group = nvim_terminal_augroup, + nvim_on('TermClose', nvim_terminal_augroup, { nested = true, desc = 'Displays the "[Process exited]" virtual text', - callback = function(ev) - if not vim.api.nvim_buf_is_valid(ev.buf) then - return - end + }, function(ev) + if not vim.api.nvim_buf_is_valid(ev.buf) then + return + end - local buf = vim.bo[ev.buf] - local pos = ev.data.pos ---@type integer - local buf_has_exitmsg = #( - vim.api.nvim_buf_get_extmarks(ev.buf, nvim_terminal_exitmsg_ns, 0, -1, {}) - ) > 0 + local buf = vim.bo[ev.buf] + local pos = ev.data.pos ---@type integer + local buf_has_exitmsg = #( + vim.api.nvim_buf_get_extmarks(ev.buf, nvim_terminal_exitmsg_ns, 0, -1, {}) + ) > 0 - -- `nvim_open_term` buffers do not have an attached 'channel'. - local msg = buf.channel == 0 and '[Terminal closed]' - or ('[Process exited %d]'):format(vim.v.event.status) + -- `nvim_open_term` buffers do not have an attached 'channel'. + local msg = buf.channel == 0 and '[Terminal closed]' + or ('[Process exited %d]'):format(vim.v.event.status) - if buf.buftype ~= 'terminal' or buf_has_exitmsg then - -- TermClose may be queued before TermOpen if process exits before `terminal_open` is called. - -- Don't display the msg now, let TermOpen display it. - vim.api.nvim_create_autocmd('TermOpen', { - buf = ev.buf, - once = true, - callback = function() - set_terminal_exitmsg(ev.buf, msg, pos) - end, - }) - return - end - set_terminal_exitmsg(ev.buf, msg, pos) - end, - }) + if buf.buftype ~= 'terminal' or buf_has_exitmsg then + -- TermClose may be queued before TermOpen if process exits before `terminal_open` is called. + -- Don't display the msg now, let TermOpen display it. + nvim_on('TermOpen', nil, { + buf = ev.buf, + once = true, + }, function() + set_terminal_exitmsg(ev.buf, msg, pos) + end) + return + end + set_terminal_exitmsg(ev.buf, msg, pos) + end) - vim.api.nvim_create_autocmd('TermRequest', { - group = nvim_terminal_augroup, + nvim_on('TermRequest', nvim_terminal_augroup, { desc = 'Handles OSC foreground/background color requests', - callback = function(ev) - --- @type integer - local channel = vim.bo[ev.buf].channel - if channel == 0 then - return + }, function(ev) + --- @type integer + local channel = vim.bo[ev.buf].channel + if channel == 0 then + return + end + local fg_request = ev.data.sequence == '\027]10;?' + local bg_request = ev.data.sequence == '\027]11;?' + if fg_request or bg_request then + -- WARN: This does not return the actual foreground/background color, + -- but rather returns: + -- - fg=white/bg=black when Nvim option 'background' is 'dark' + -- - fg=black/bg=white when Nvim option 'background' is 'light' + local red, green, blue = 0, 0, 0 + local bg_option_dark = vim.o.background == 'dark' + if (fg_request and bg_option_dark) or (bg_request and not bg_option_dark) then + red, green, blue = 65535, 65535, 65535 end - local fg_request = ev.data.sequence == '\027]10;?' - local bg_request = ev.data.sequence == '\027]11;?' - if fg_request or bg_request then - -- WARN: This does not return the actual foreground/background color, - -- but rather returns: - -- - fg=white/bg=black when Nvim option 'background' is 'dark' - -- - fg=black/bg=white when Nvim option 'background' is 'light' - local red, green, blue = 0, 0, 0 - local bg_option_dark = vim.o.background == 'dark' - if (fg_request and bg_option_dark) or (bg_request and not bg_option_dark) then - red, green, blue = 65535, 65535, 65535 - end - local command = fg_request and 10 or 11 - local data = string.format( - '\027]%d;rgb:%04x/%04x/%04x%s', - command, - red, - green, - blue, - ev.data.terminator - ) - vim.api.nvim_chan_send(channel, data) - end - end, - }) + local command = fg_request and 10 or 11 + local data = + string.format('\027]%d;rgb:%04x/%04x/%04x%s', command, red, green, blue, ev.data.terminator) + vim.api.nvim_chan_send(channel, data) + end + end) local nvim_terminal_prompt_ns = vim.api.nvim_create_namespace('nvim.terminal.prompt') - vim.api.nvim_create_autocmd('TermRequest', { - group = nvim_terminal_augroup, + nvim_on('TermRequest', nvim_terminal_augroup, { desc = 'Mark shell prompts indicated by OSC 133 sequences for navigation', - callback = function(ev) - if string.match(ev.data.sequence, '^\027]133;A') then - local lnum = ev.data.cursor[1] ---@type integer - if lnum >= 1 then - vim.api.nvim_buf_set_extmark( - ev.buf, - nvim_terminal_prompt_ns, - lnum - 1, - 0, - { right_gravity = false } - ) - end + }, function(ev) + if string.match(ev.data.sequence, '^\027]133;A') then + local lnum = ev.data.cursor[1] ---@type integer + if lnum >= 1 then + vim.api.nvim_buf_set_extmark( + ev.buf, + nvim_terminal_prompt_ns, + lnum - 1, + 0, + { right_gravity = false } + ) end - end, - }) + end + end) ---@param ns integer ---@param buf integer @@ -732,39 +714,37 @@ do end end - vim.api.nvim_create_autocmd('TermOpen', { - group = nvim_terminal_augroup, + nvim_on('TermOpen', nvim_terminal_augroup, { desc = 'Default settings for :terminal buffers', - callback = function(ev) - vim.bo[ev.buf].modifiable = false - vim.bo[ev.buf].undolevels = -1 - vim.bo[ev.buf].scrollback = vim.o.scrollback < 0 and 10000 or math.max(1, vim.o.scrollback) - vim.bo[ev.buf].textwidth = 0 - vim.wo[0][0].wrap = false - vim.wo[0][0].list = false - vim.wo[0][0].number = false - vim.wo[0][0].relativenumber = false - vim.wo[0][0].signcolumn = 'no' - vim.wo[0][0].foldcolumn = '0' + }, function(ev) + vim.bo[ev.buf].modifiable = false + vim.bo[ev.buf].undolevels = -1 + vim.bo[ev.buf].scrollback = vim.o.scrollback < 0 and 10000 or math.max(1, vim.o.scrollback) + vim.bo[ev.buf].textwidth = 0 + vim.wo[0][0].wrap = false + vim.wo[0][0].list = false + vim.wo[0][0].number = false + vim.wo[0][0].relativenumber = false + vim.wo[0][0].signcolumn = 'no' + vim.wo[0][0].foldcolumn = '0' - -- This is gross. Proper list options support when? - local winhl = vim.o.winhighlight - if winhl ~= '' then - winhl = winhl .. ',' - end - vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC' + -- This is gross. Proper list options support when? + local winhl = vim.o.winhighlight + if winhl ~= '' then + winhl = winhl .. ',' + end + vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC' - vim.keymap.set({ 'n', 'x', 'o' }, '[[', function() - jump_to_prompt(nvim_terminal_prompt_ns, 0, ev.buf, -vim.v.count1) - end, { buf = ev.buf, desc = 'Jump [count] shell prompts backward' }) - vim.keymap.set({ 'n', 'x', 'o' }, ']]', function() - jump_to_prompt(nvim_terminal_prompt_ns, 0, ev.buf, vim.v.count1) - end, { buf = ev.buf, desc = 'Jump [count] shell prompts forward' }) + vim.keymap.set({ 'n', 'x', 'o' }, '[[', function() + jump_to_prompt(nvim_terminal_prompt_ns, 0, ev.buf, -vim.v.count1) + end, { buf = ev.buf, desc = 'Jump [count] shell prompts backward' }) + vim.keymap.set({ 'n', 'x', 'o' }, ']]', function() + jump_to_prompt(nvim_terminal_prompt_ns, 0, ev.buf, vim.v.count1) + end, { buf = ev.buf, desc = 'Jump [count] shell prompts forward' }) - -- If the terminal buffer is being reused, clear the previous exit msg - vim.api.nvim_buf_clear_namespace(ev.buf, nvim_terminal_exitmsg_ns, 0, -1) - end, - }) + -- If the terminal buffer is being reused, clear the previous exit msg + vim.api.nvim_buf_clear_namespace(ev.buf, nvim_terminal_exitmsg_ns, 0, -1) + end) vim.api.nvim_create_autocmd('CmdwinEnter', { pattern = '[:>]', @@ -773,26 +753,24 @@ do command = 'syntax sync minlines=1 maxlines=1', }) - vim.api.nvim_create_autocmd('SwapExists', { + nvim_on('SwapExists', vim.api.nvim_create_augroup('nvim.swapfile', {}), { pattern = '*', desc = 'Skip the swapfile prompt when the swapfile is owned by a running Nvim process', - group = vim.api.nvim_create_augroup('nvim.swapfile', {}), - callback = function() - local info = vim.fn.swapinfo(vim.v.swapname) - local user = vim.uv.os_get_passwd().username - local iswin = 1 == vim.fn.has('win32') - if info.error or info.pid <= 0 or (not iswin and info.user ~= user) then - vim.v.swapchoice = '' -- Show the prompt. - return - end - vim.v.swapchoice = 'e' -- Choose "(E)dit". - vim.notify( - ('W325: Ignoring swapfile from Nvim process %d'):format(info.pid), - vim.log.levels.WARN, - { _truncate = true } - ) - end, - }) + }, function() + local info = vim.fn.swapinfo(vim.v.swapname) + local user = vim.uv.os_get_passwd().username + local iswin = 1 == vim.fn.has('win32') + if info.error or info.pid <= 0 or (not iswin and info.user ~= user) then + vim.v.swapchoice = '' -- Show the prompt. + return + end + vim.v.swapchoice = 'e' -- Choose "(E)dit". + vim.notify( + ('W325: Ignoring swapfile from Nvim process %d'):format(info.pid), + vim.log.levels.WARN, + { _truncate = true } + ) + end) -- Check if a TTY is attached local tty = nil @@ -824,14 +802,12 @@ do --- @diagnostic disable-next-line:no-unknown vim.o[option] = value else - vim.api.nvim_create_autocmd('VimEnter', { - group = group, + nvim_on('VimEnter', group, { once = true, nested = true, - callback = function() - setoption(option, value, force) - end, - }) + }, function() + setoption(option, value, force) + end) end end @@ -950,22 +926,20 @@ do end end) - vim.api.nvim_create_autocmd('VimEnter', { - group = group, + nvim_on('VimEnter', group, { nested = true, once = true, - callback = function() - local optinfo = vim.api.nvim_get_option_info2('background', {}) - local sid_lua = -8 - if - optinfo.was_set - and optinfo.last_set_sid ~= sid_lua - and next(vim.api.nvim_get_autocmds({ id = id })) ~= nil - then - vim.api.nvim_del_autocmd(id) - end - end, - }) + }, function() + local optinfo = vim.api.nvim_get_option_info2('background', {}) + local sid_lua = -8 + if + optinfo.was_set + and optinfo.last_set_sid ~= sid_lua + and next(vim.api.nvim_get_autocmds({ id = id })) ~= nil + then + vim.api.nvim_del_autocmd(id) + end + end) -- Wait until detection of OSC 11 capabilities is complete to -- ensure background is automatically set before user config. @@ -1068,22 +1042,20 @@ do if tty then -- Show progress bars in supporting terminals - vim.api.nvim_create_autocmd('Progress', { - group = vim.api.nvim_create_augroup('nvim.progress', {}), + nvim_on('Progress', vim.api.nvim_create_augroup('nvim.progress', {}), { desc = 'Display native progress bars', - callback = function(ev) - if ev.data.status == 'running' then - if ev.data.percent ~= nil then - vim.api.nvim_ui_send(string.format('\027]9;4;1;%d\027\\', ev.data.percent)) - else - -- "Indeterminate" progress (unknown percent). - vim.api.nvim_ui_send(string.format('\027]9;4;3\027\\')) - end + }, function(ev) + if ev.data.status == 'running' then + if ev.data.percent ~= nil then + vim.api.nvim_ui_send(string.format('\027]9;4;1;%d\027\\', ev.data.percent)) else - vim.api.nvim_ui_send('\027]9;4;0;0\027\\') + -- "Indeterminate" progress (unknown percent). + vim.api.nvim_ui_send(string.format('\027]9;4;3\027\\')) end - end, - }) + else + vim.api.nvim_ui_send('\027]9;4;0;0\027\\') + end + end) end end diff --git a/runtime/lua/vim/_core/ui2.lua b/runtime/lua/vim/_core/ui2.lua index 2de47b8105..619abde1c4 100644 --- a/runtime/lua/vim/_core/ui2.lua +++ b/runtime/lua/vim/_core/ui2.lua @@ -49,6 +49,7 @@ --- - |g<| at any time. local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local M = { ns = api.nvim_create_namespace('nvim.ui2'), augroup = api.nvim_create_augroup('nvim.ui2', {}), @@ -229,30 +230,26 @@ function M.enable(opts) end) end - api.nvim_create_autocmd('OptionSet', { - group = M.augroup, + nvim_on('OptionSet', M.augroup, { pattern = { 'cmdheight', 'laststatus' }, - callback = function(ev) - if ev.match == 'cmdheight' then - check_cmdheight(vim.v.option_new) - end - M.msg.set_pos() - end, desc = 'Set cmdline and message window dimensions for changed option values.', - }) + }, function(ev) + if ev.match == 'cmdheight' then + check_cmdheight(vim.v.option_new) + end + M.msg.set_pos() + end) - api.nvim_create_autocmd({ 'VimResized', 'TabEnter' }, { - group = M.augroup, - callback = function(ev) - M.check_targets() - -- After a tabpage was closed unhide the msg window on the current tabpage. - if ev.event == 'TabEnter' and next(M.msg.msg.ids) ~= nil then - api.nvim_win_set_config(M.wins.msg, { hide = false, width = M.msg.msg.width }) - end - M.msg.set_pos() - end, + nvim_on({ 'VimResized', 'TabEnter' }, M.augroup, { desc = 'Set cmdline and message window dimensions after shell resize or tabpage change.', - }) + }, function(ev) + M.check_targets() + -- After a tabpage was closed unhide the msg window on the current tabpage. + if ev.event == 'TabEnter' and next(M.msg.msg.ids) ~= nil then + api.nvim_win_set_config(M.wins.msg, { hide = false, width = M.msg.msg.width }) + end + M.msg.set_pos() + end) end return M diff --git a/runtime/lua/vim/_core/ui2/messages.lua b/runtime/lua/vim/_core/ui2/messages.lua index be93021629..b50acd009a 100644 --- a/runtime/lua/vim/_core/ui2/messages.lua +++ b/runtime/lua/vim/_core/ui2/messages.lua @@ -1,4 +1,5 @@ local api, fn, o = vim.api, vim.fn, vim.o +local nvim_on = require('vim._core.util').nvim_on local ui = require('vim._core.ui2') ---@alias Msg { extid: integer, timer: uv.uv_timer_t? } @@ -634,35 +635,33 @@ local function enter_pager() local height, id = api.nvim_win_get_height(ui.wins.pager), 0 api.nvim_set_option_value('eiw', '', { scope = 'local', win = ui.wins.pager }) api.nvim_set_current_win(ui.wins.pager) - id = api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'WinResized' }, { - group = ui.augroup, - callback = function(ev) - if fn.getcmdtype() ~= '' then - -- WinEnter fires before we can detect cmdwin will be entered: keep open. - return - elseif ev.event == 'WinResized' and fn.getcmdwintype() == '' then - -- Remember height to be restored when cmdwin is closed. - height = api.nvim_win_get_height(ui.wins.pager) - elseif ev.event == 'WinEnter' then - -- Close when no longer current window. - in_pager = api.nvim_get_current_win() == ui.wins.pager - end - in_pager = in_pager and api.nvim_win_is_valid(ui.wins.pager) - local cfg = in_pager and { relative = 'laststatus', col = 0 } or { hide = true } - if in_pager then - cfg.row, cfg.height, cfg.border = win_row_height_border('pager', height) - else - pcall(api.nvim_set_option_value, 'eiw', 'all', { scope = 'local', win = ui.wins.pager }) - api.nvim_del_autocmd(id) - if was_cmdwin ~= '' then - api.nvim_feedkeys('q' .. was_cmdwin, 'n', false) - was_cmdwin = '' - end - end - pcall(api.nvim_win_set_config, ui.wins.pager, cfg) - end, + id = nvim_on({ 'WinEnter', 'CmdwinEnter', 'WinResized' }, ui.augroup, { desc = 'Hide or reposition pager window.', - }) + }, function(ev) + if fn.getcmdtype() ~= '' then + -- WinEnter fires before we can detect cmdwin will be entered: keep open. + return + elseif ev.event == 'WinResized' and fn.getcmdwintype() == '' then + -- Remember height to be restored when cmdwin is closed. + height = api.nvim_win_get_height(ui.wins.pager) + elseif ev.event == 'WinEnter' then + -- Close when no longer current window. + in_pager = api.nvim_get_current_win() == ui.wins.pager + end + in_pager = in_pager and api.nvim_win_is_valid(ui.wins.pager) + local cfg = in_pager and { relative = 'laststatus', col = 0 } or { hide = true } + if in_pager then + cfg.row, cfg.height, cfg.border = win_row_height_border('pager', height) + else + pcall(api.nvim_set_option_value, 'eiw', 'all', { scope = 'local', win = ui.wins.pager }) + api.nvim_del_autocmd(id) + if was_cmdwin ~= '' then + api.nvim_feedkeys('q' .. was_cmdwin, 'n', false) + was_cmdwin = '' + end + end + pcall(api.nvim_win_set_config, ui.wins.pager, cfg) + end) end) end diff --git a/runtime/lua/vim/_core/util.lua b/runtime/lua/vim/_core/util.lua index 57e31e3bd7..0f56256558 100644 --- a/runtime/lua/vim/_core/util.lua +++ b/runtime/lua/vim/_core/util.lua @@ -172,4 +172,33 @@ function M.echo_err(msg) vim.api.nvim_echo({ { msg } }, true, { err = true }) end +--- Define event-handlers (autocmds) ergonomically. +--- +--- Examples: +--- ```lua +--- local nvim_on = require('vim._core.util').nvim_on +--- nvim_on('BufWritePost', group, function(ev) print(ev.file) end) +--- nvim_on({ 'BufRead', 'BufNew' }, group, { pattern = '*.lua' }, function(ev) end) +--- nvim_on('VimLeavePre', nil, function() end) +--- ``` +--- +--- @param events vim.api.keyset.events|vim.api.keyset.events[] Event(s) to watch. See |autocmd-events|. +--- @param group string|integer? Group name or id, or `nil`. +--- @param opts_or_fn vim.api.keyset.create_autocmd Options. +--- @param fn fun(ev: vim.api.keyset.create_autocmd.callback_args): boolean? Event handler. +--- @return integer # Autocmd id (see |nvim_create_autocmd()|). +--- @overload fun(events: vim.api.keyset.events|vim.api.keyset.events[], group: string|integer?, fn: fun(ev: vim.api.keyset.create_autocmd.callback_args): boolean?): integer +function M.nvim_on(events, group, opts_or_fn, fn) + vim.validate('opts_or_fn', opts_or_fn, { 'function', 'table' }) + local opts --- @type vim.api.keyset.create_autocmd + if type(opts_or_fn) == 'function' then + fn, opts = opts_or_fn, {} + else + opts = opts_or_fn + end + opts.group = group + opts.callback = fn + return vim.api.nvim_create_autocmd(events, opts) +end + return M diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index a7f29029c9..39b87be93f 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1,4 +1,5 @@ local api = vim.api +local nvim_on = require('vim._core.util').nvim_on -- TODO(lewis6991): deprecate some top level functions in favour of the submodule version -- e.g. vim.diagnostic.get_namespace() -> vim.diagnostic.namespace.get() @@ -1139,14 +1140,12 @@ function M.status(buf) return result_str end -api.nvim_create_autocmd('DiagnosticChanged', { - group = api.nvim_create_augroup('nvim.diagnostic.status', {}), - callback = function(ev) - if api.nvim_buf_is_loaded(ev.buf) then - api.nvim__redraw({ buf = ev.buf, statusline = true }) - end - end, +nvim_on('DiagnosticChanged', api.nvim_create_augroup('nvim.diagnostic.status', {}), { desc = 'diagnostics component for the statusline', -}) +}, function(ev) + if api.nvim_buf_is_loaded(ev.buf) then + api.nvim__redraw({ buf = ev.buf, statusline = true }) + end +end) return M diff --git a/runtime/lua/vim/diagnostic/_display.lua b/runtime/lua/vim/diagnostic/_display.lua index 8148f32ec5..00fb166cee 100644 --- a/runtime/lua/vim/diagnostic/_display.lua +++ b/runtime/lua/vim/diagnostic/_display.lua @@ -1,4 +1,5 @@ local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local diagnostic_modules = vim._defer_require('vim.diagnostic', { _config = ..., --- @module 'vim.diagnostic._config' @@ -62,14 +63,12 @@ local function schedule_display(namespace, bufnr, args) local key = make_augroup_key(namespace, bufnr) if not registered_autocmds[key] then local group = api.nvim_create_augroup(key, { clear = true }) - api.nvim_create_autocmd(insert_leave_auto_cmds, { - group = group, + nvim_on(insert_leave_auto_cmds, group, { buf = bufnr, - callback = function() - execute_scheduled_display(namespace, bufnr) - end, desc = 'vim.diagnostic: display diagnostics', - }) + }, function() + execute_scheduled_display(namespace, bufnr) + end) registered_autocmds[key] = true end end diff --git a/runtime/lua/vim/diagnostic/_handlers.lua b/runtime/lua/vim/diagnostic/_handlers.lua index abe28cf945..d1a3be93a5 100644 --- a/runtime/lua/vim/diagnostic/_handlers.lua +++ b/runtime/lua/vim/diagnostic/_handlers.lua @@ -1,4 +1,5 @@ local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local diagnostic = vim.diagnostic local diagnostic_shared = require('vim.diagnostic._shared') @@ -123,13 +124,9 @@ local function once_buf_loaded(bufnr, fn) if api.nvim_buf_is_loaded(bufnr) then fn() else - return api.nvim_create_autocmd('BufRead', { - buf = bufnr, - once = true, - callback = function() - fn() - end, - }) + return nvim_on('BufRead', nil, { buf = bufnr, once = true }, function() + fn() + end) end end @@ -436,13 +433,9 @@ function M.virtual_text.show(namespace, bufnr, diagnostics, opts) local line_diagnostics = diagnostic_shared.diagnostic_lines(diagnostics, true) if vopts.current_line ~= nil then - api.nvim_create_autocmd('CursorMoved', { - buf = bufnr, - group = ns.user_data.virt_text_augroup, - callback = function() - render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, vopts) - end, - }) + nvim_on('CursorMoved', ns.user_data.virt_text_augroup, { buf = bufnr }, function() + render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, vopts) + end) end render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, vopts) @@ -690,17 +683,13 @@ function M.virtual_lines.show(namespace, bufnr, diagnostics, opts) -- Create a mapping from line -> diagnostics so that we can quickly get the -- diagnostics we need when the cursor line doesn't change. local line_diagnostics = diagnostic_shared.diagnostic_lines(diagnostics, true) - api.nvim_create_autocmd('CursorMoved', { - buf = bufnr, - group = ns.user_data.virt_lines_augroup, - callback = function() - render_virtual_lines( - ns.user_data.virt_lines_ns, - bufnr, - diagnostic_shared.diagnostics_at_cursor(line_diagnostics) - ) - end, - }) + nvim_on('CursorMoved', ns.user_data.virt_lines_augroup, { buf = bufnr }, function() + render_virtual_lines( + ns.user_data.virt_lines_ns, + bufnr, + diagnostic_shared.diagnostics_at_cursor(line_diagnostics) + ) + end) -- Also show diagnostics for the current line before the first CursorMoved event. render_virtual_lines( diff --git a/runtime/lua/vim/diagnostic/_store.lua b/runtime/lua/vim/diagnostic/_store.lua index 6e11bc2022..aef06dc8d9 100644 --- a/runtime/lua/vim/diagnostic/_store.lua +++ b/runtime/lua/vim/diagnostic/_store.lua @@ -1,4 +1,5 @@ local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local severity_module = require('vim.diagnostic._severity') @@ -14,13 +15,9 @@ setmetatable(diagnostic_cache, { --- @param bufnr integer __index = function(t, bufnr) assert(bufnr > 0, 'Invalid buffer number') - api.nvim_create_autocmd('BufWipeout', { - group = group, - buf = bufnr, - callback = function() - rawset(t, bufnr, nil) - end, - }) + nvim_on('BufWipeout', group, { buf = bufnr }, function() + rawset(t, bufnr, nil) + end) t[bufnr] = {} return t[bufnr] end, @@ -50,13 +47,12 @@ local function once_buf_loaded(bufnr, fn) if api.nvim_buf_is_loaded(bufnr) then fn() else - return api.nvim_create_autocmd('BufRead', { + return nvim_on('BufRead', nil, { buf = bufnr, once = true, - callback = function() - fn() - end, - }) + }, function() + fn() + end) end end diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua index 2b52296fc5..0fa218772d 100644 --- a/runtime/lua/vim/health/health.lua +++ b/runtime/lua/vim/health/health.lua @@ -1,3 +1,5 @@ +local nvim_on = require('vim._core.util').nvim_on + local M = {} local health = require('vim.health') @@ -707,31 +709,27 @@ local function check_sysinfo() ) ) - vim.api.nvim_create_autocmd('FileType', { - pattern = 'checkhealth', - once = true, - callback = function(ev) - local buf = ev.buf - local win = vim.fn.bufwinid(buf) - if win == -1 then - return - end - local encoded_body = vim.uri_encode(body) --- @type string - local issue_url = 'https://github.com/neovim/neovim/issues/new?type=Bug&body=' .. encoded_body + nvim_on('FileType', nil, { pattern = 'checkhealth', once = true }, function(ev) + local buf = ev.buf + local win = vim.fn.bufwinid(buf) + if win == -1 then + return + end + local encoded_body = vim.uri_encode(body) --- @type string + local issue_url = 'https://github.com/neovim/neovim/issues/new?type=Bug&body=' .. encoded_body - _G.nvim_health_bugreport_open = function() - vim.ui.open(issue_url) - end - vim.wo[win].winbar = - '%#WarningMsg#%@v:lua.nvim_health_bugreport_open@▶ Create Bug Report on GitHub%X%*' + _G.nvim_health_bugreport_open = function() + vim.ui.open(issue_url) + end + vim.wo[win].winbar = + '%#WarningMsg#%@v:lua.nvim_health_bugreport_open@▶ Create Bug Report on GitHub%X%*' - vim.api.nvim_create_autocmd('BufDelete', { - buf = buf, - once = true, - command = 'lua _G.nvim_health_bugreport_open = nil', - }) - end, - }) + vim.api.nvim_create_autocmd('BufDelete', { + buf = buf, + once = true, + command = 'lua _G.nvim_health_bugreport_open = nil', + }) + end) end function M.check() diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 9ede441bde..6e989cfc52 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1,5 +1,6 @@ local api = vim.api local validate = vim.validate +local nvim_on = require('vim._core.util').nvim_on local lsp = vim._defer_require('vim.lsp', { _capability = ..., --- @module 'vim.lsp._capability' @@ -643,12 +644,9 @@ function lsp.enable(name, enable) else -- Only ever create autocmd once to reuse computation of config merging. lsp_enable_autocmd_id = lsp_enable_autocmd_id - or api.nvim_create_autocmd('FileType', { - group = api.nvim_create_augroup('nvim.lsp.enable', {}), - callback = function(ev) - lsp_enable_callback(ev.buf) - end, - }) + or nvim_on('FileType', api.nvim_create_augroup('nvim.lsp.enable', {}), function(ev) + lsp_enable_callback(ev.buf) + end) end -- Ensure any pre-existing buffers start/stop their LSP clients. @@ -909,41 +907,37 @@ local function buf_attach(bufnr) local uri = vim.uri_from_bufnr(bufnr) local augroup = ('nvim.lsp.b_%d_save'):format(bufnr) local group = api.nvim_create_augroup(augroup, { clear = true }) - api.nvim_create_autocmd('BufWritePre', { - group = group, + nvim_on('BufWritePre', group, { buf = bufnr, desc = 'vim.lsp: textDocument/willSave', - callback = function(ctx) - for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do - local params = { - textDocument = { - uri = uri, - }, - reason = protocol.TextDocumentSaveReason.Manual, ---@type integer - } - if client:supports_method('textDocument/willSave') then - client:notify('textDocument/willSave', params) - end - if client:supports_method('textDocument/willSaveWaitUntil') then - local result, err = - client:request_sync('textDocument/willSaveWaitUntil', params, 1000, ctx.buf) - if result and result.result then - util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) - elseif err then - log.error(vim.inspect(err)) - end + }, function(ctx) + for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do + local params = { + textDocument = { + uri = uri, + }, + reason = protocol.TextDocumentSaveReason.Manual, ---@type integer + } + if client:supports_method('textDocument/willSave') then + client:notify('textDocument/willSave', params) + end + if client:supports_method('textDocument/willSaveWaitUntil') then + local result, err = + client:request_sync('textDocument/willSaveWaitUntil', params, 1000, ctx.buf) + if result and result.result then + util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) + elseif err then + log.error(vim.inspect(err)) end end - end, - }) - api.nvim_create_autocmd('BufWritePost', { - group = group, + end + end) + nvim_on('BufWritePost', group, { buf = bufnr, desc = 'vim.lsp: textDocument/didSave handler', - callback = function(ctx) - text_document_did_save_handler(ctx.buf) - end, - }) + }, function(ctx) + text_document_did_save_handler(ctx.buf) + end) -- First time, so attach and set up stuff. api.nvim_buf_attach(bufnr, false, { on_lines = function(_, _, changedtick, firstline, lastline, new_lastline) @@ -1169,42 +1163,36 @@ end -- Minimum time before warning about LSP exit_timeout on Nvim exit. local min_warn_exit_timeout = 100 -api.nvim_create_autocmd('VimLeavePre', { - desc = 'vim.lsp: exit handler', - callback = function() - local active_clients = lsp.get_clients() - log.info('exit_handler', active_clients) +nvim_on('VimLeavePre', nil, { desc = 'vim.lsp: exit handler' }, function() + local active_clients = lsp.get_clients() + log.info('exit_handler', active_clients) - local max_timeout = 0 - for _, client in pairs(active_clients) do - max_timeout = math.max(max_timeout, vim._tointeger(client.exit_timeout) or 0) - client:stop(client.exit_timeout) - end + local max_timeout = 0 + for _, client in pairs(active_clients) do + max_timeout = math.max(max_timeout, vim._tointeger(client.exit_timeout) or 0) + client:stop(client.exit_timeout) + end - local exit_warning_timer = max_timeout > min_warn_exit_timeout - and vim.defer_fn(function() - api.nvim_echo({ - { - string.format( - 'Waiting %ss for LSP exit (Press Ctrl-C to force exit)', - max_timeout / 1e3 - ), - 'WarningMsg', - }, - }, true, {}) - end, min_warn_exit_timeout) + local exit_warning_timer = max_timeout > min_warn_exit_timeout + and vim.defer_fn(function() + api.nvim_echo({ + { + string.format('Waiting %ss for LSP exit (Press Ctrl-C to force exit)', max_timeout / 1e3), + 'WarningMsg', + }, + }, true, {}) + end, min_warn_exit_timeout) - vim.wait(max_timeout, function() - return vim.iter(active_clients):all(function(client) - return client.rpc.is_closing() - end) + vim.wait(max_timeout, function() + return vim.iter(active_clients):all(function(client) + return client.rpc.is_closing() end) + end) - if exit_warning_timer and not exit_warning_timer:is_closing() then - exit_warning_timer:close() - end - end, -}) + if exit_warning_timer and not exit_warning_timer:is_closing() then + exit_warning_timer:close() + end +end) ---@nodoc --- Sends an async request for all active clients attached to the diff --git a/runtime/lua/vim/lsp/_folding_range.lua b/runtime/lua/vim/lsp/_folding_range.lua index 2d92b12110..de9dffa9d0 100644 --- a/runtime/lua/vim/lsp/_folding_range.lua +++ b/runtime/lua/vim/lsp/_folding_range.lua @@ -1,6 +1,7 @@ local util = require('vim.lsp.util') local log = require('vim.lsp.log') local tableclear = require('vim._core.table').clear +local nvim_on = require('vim._core.util').nvim_on local api = vim.api ---@type table @@ -116,14 +117,10 @@ local scheduled_foldupdate = {} local function schedule_foldupdate(bufnr) if not scheduled_foldupdate[bufnr] then scheduled_foldupdate[bufnr] = true - api.nvim_create_autocmd('InsertLeave', { - buf = bufnr, - once = true, - callback = function() - foldupdate(bufnr) - scheduled_foldupdate[bufnr] = nil - end, - }) + nvim_on('InsertLeave', nil, { buf = bufnr, once = true }, function() + foldupdate(bufnr) + scheduled_foldupdate[bufnr] = nil + end) end end @@ -238,35 +235,23 @@ function State:new(bufnr) end end, }) - api.nvim_create_autocmd('LspNotify', { - group = self.augroup, - buf = bufnr, - callback = function(ev) - local client = assert(vim.lsp.get_client_by_id(ev.data.client_id)) - if - client:supports_method('textDocument/foldingRange', bufnr) - and (ev.data.method == 'textDocument/didChange' or ev.data.method == 'textDocument/didOpen') - then - self:refresh(client) - end - end, - }) - api.nvim_create_autocmd('OptionSet', { - group = self.augroup, - pattern = 'foldexpr', - callback = function() - if vim.v.option_type == 'global' or api.nvim_get_current_buf() == bufnr then - vim.lsp._capability.enable('folding_range', false, { bufnr = bufnr }) - end - end, - }) - api.nvim_create_autocmd('FileType', { - group = self.augroup, - buf = bufnr, - callback = function() - self:reset() - end, - }) + nvim_on('LspNotify', self.augroup, { buf = bufnr }, function(ev) + local client = assert(vim.lsp.get_client_by_id(ev.data.client_id)) + if + client:supports_method('textDocument/foldingRange', bufnr) + and (ev.data.method == 'textDocument/didChange' or ev.data.method == 'textDocument/didOpen') + then + self:refresh(client) + end + end) + nvim_on('OptionSet', self.augroup, { pattern = 'foldexpr' }, function() + if vim.v.option_type == 'global' or api.nvim_get_current_buf() == bufnr then + vim.lsp._capability.enable('folding_range', false, { bufnr = bufnr }) + end + end) + nvim_on('FileType', self.augroup, { buf = bufnr }, function() + self:reset() + end) return self end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 94fc21dd27..306ffa5c0d 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -2,6 +2,7 @@ --- The `vim.lsp.buf_…` functions perform operations for LSP clients attached to the current buffer. local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local lsp = vim.lsp local validate = vim.validate local util = require('vim.lsp.util') @@ -188,14 +189,10 @@ function M.hover(config) local _, winid = lsp.util.open_floating_preview(contents, format, config) - api.nvim_create_autocmd('WinClosed', { - pattern = tostring(winid), - once = true, - callback = function() - api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1) - return true - end, - }) + nvim_on('WinClosed', nil, { pattern = tostring(winid), once = true }, function() + api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1) + return true + end) end) end @@ -1509,13 +1506,9 @@ function M.selection_range(direction, timeout_ms) end -- Clear selection ranges when leaving visual mode. - api.nvim_create_autocmd('ModeChanged', { - once = true, - pattern = 'v*:*', - callback = function() - selection_ranges = nil - end, - }) + nvim_on('ModeChanged', nil, { once = true, pattern = 'v*:*' }, function() + selection_ranges = nil + end) if #ranges > 0 then local index = math.min(#ranges, math.max(1, direction)) diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index a096d778b4..3b9201f143 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -34,6 +34,7 @@ local M = {} local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local lsp = vim.lsp local protocol = lsp.protocol @@ -789,55 +790,53 @@ end --- Defines a CompleteChanged handler to request and display LSP completion item documentation --- via completionItem/resolve local function on_completechanged(group, bufnr) - api.nvim_create_autocmd('CompleteChanged', { - group = group, + nvim_on('CompleteChanged', group, { buf = bufnr, - callback = function(ev) - local completed_item = vim.v.event.completed_item or {} - local lsp_item = vim.tbl_get(completed_item, 'user_data', 'nvim', 'lsp', 'completion_item') - local data = vim.fn.complete_info({ 'selected' }) - if (completed_item.info or '') ~= '' then - local kind = vim.tbl_get(lsp_item or {}, 'documentation', 'kind') - update_popup_window( - data.preview_winid, - data.preview_bufnr, - kind or protocol.MarkupKind.Markdown - ) - return - end - - if - #lsp.get_clients({ - id = vim.tbl_get(completed_item, 'user_data', 'nvim', 'lsp', 'client_id'), - method = 'completionItem/resolve', - bufnr = ev.buf, - }) == 0 - then - if - has_completeopt('popup') - and lsp_item - and lsp_item.insertTextFormat == protocol.InsertTextFormat.Snippet - then - -- Shows snippet preview in doc popup if completeopt=popup. - local text = parse_snippet(lsp_item.insertText or lsp_item.textEdit.newText) - local windata = api.nvim__complete_set( - data.selected, - { info = ('```%s\n%s\n```'):format(vim.bo.filetype, text) } - ) - update_popup_window(windata.winid, windata.bufnr, protocol.MarkupKind.Markdown) - end - return - end - - -- Retrieve the raw LSP completionItem from completed_item as the parameter for - -- the completionItem/resolve request - if lsp_item then - Context.resolve_handler = Context.resolve_handler or CompletionResolver.new() - Context.resolve_handler:request(ev.buf, lsp_item, completed_item.word) - end - end, desc = 'Request and display LSP completion item documentation via completionItem/resolve', - }) + }, function(ev) + local completed_item = vim.v.event.completed_item or {} + local lsp_item = vim.tbl_get(completed_item, 'user_data', 'nvim', 'lsp', 'completion_item') + local data = vim.fn.complete_info({ 'selected' }) + if (completed_item.info or '') ~= '' then + local kind = vim.tbl_get(lsp_item or {}, 'documentation', 'kind') + update_popup_window( + data.preview_winid, + data.preview_bufnr, + kind or protocol.MarkupKind.Markdown + ) + return + end + + if + #lsp.get_clients({ + id = vim.tbl_get(completed_item, 'user_data', 'nvim', 'lsp', 'client_id'), + method = 'completionItem/resolve', + bufnr = ev.buf, + }) == 0 + then + if + has_completeopt('popup') + and lsp_item + and lsp_item.insertTextFormat == protocol.InsertTextFormat.Snippet + then + -- Shows snippet preview in doc popup if completeopt=popup. + local text = parse_snippet(lsp_item.insertText or lsp_item.textEdit.newText) + local windata = api.nvim__complete_set( + data.selected, + { info = ('```%s\n%s\n```'):format(vim.bo.filetype, text) } + ) + update_popup_window(windata.winid, windata.bufnr, protocol.MarkupKind.Markdown) + end + return + end + + -- Retrieve the raw LSP completionItem from completed_item as the parameter for + -- the completionItem/resolve request + if lsp_item then + Context.resolve_handler = Context.resolve_handler or CompletionResolver.new() + Context.resolve_handler:request(ev.buf, lsp_item, completed_item.word) + end + end) end local function on_complete_done() @@ -941,16 +940,12 @@ local function register_completedone(bufnr) return group end - api.nvim_create_autocmd('CompleteDone', { - group = group, - buf = bufnr, - callback = function() - local reason = api.nvim_get_vvar('event').reason ---@type string - if reason == 'accept' then - on_complete_done() - end - end, - }) + nvim_on('CompleteDone', group, { buf = bufnr }, function() + local reason = api.nvim_get_vvar('event').reason ---@type string + if reason == 'accept' then + on_complete_done() + end + end) return group end @@ -1167,28 +1162,18 @@ local function enable_completions(client_id, bufnr, opts) -- Set up autocommands. local group = register_completedone(bufnr) - api.nvim_create_autocmd('LspDetach', { - group = group, + nvim_on('LspDetach', group, { buf = bufnr, desc = 'vim.lsp.completion: clean up client on detach', - callback = function(ev) - disable_completions(ev.data.client_id, ev.buf) - end, - }) + }, function(ev) + disable_completions(ev.data.client_id, ev.buf) + end) if opts.autotrigger then - api.nvim_create_autocmd('InsertCharPre', { - group = group, - buf = bufnr, - callback = function() - on_insert_char_pre(buf_handles[bufnr]) - end, - }) - api.nvim_create_autocmd('InsertLeave', { - group = group, - buf = bufnr, - callback = on_insert_leave, - }) + nvim_on('InsertCharPre', group, { buf = bufnr }, function() + on_insert_char_pre(buf_handles[bufnr]) + end) + nvim_on('InsertLeave', group, { buf = bufnr }, on_insert_leave) end end diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index ec8801567e..f0dec95be0 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -8,6 +8,7 @@ local protocol = lsp.protocol local util = lsp.util local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local M = {} @@ -416,22 +417,18 @@ function M._enable(bufnr) bufstates[bufnr] = { pull_kind = 'document', client_result_id = {} } end - api.nvim_create_autocmd('LspNotify', { - buf = bufnr, - callback = function(opts) - if - opts.data.method ~= 'textDocument/didChange' - and opts.data.method ~= 'textDocument/didOpen' - then - return - end - if bufstates[bufnr] and bufstates[bufnr].pull_kind == 'document' then - local client_id = opts.data.client_id --- @type integer? - M._refresh(bufnr, client_id, true) - end - end, - group = augroup, - }) + nvim_on('LspNotify', augroup, { buf = bufnr }, function(opts) + if + opts.data.method ~= 'textDocument/didChange' + and opts.data.method ~= 'textDocument/didOpen' + then + return + end + if bufstates[bufnr] and bufstates[bufnr].pull_kind == 'document' then + local client_id = opts.data.client_id --- @type integer? + M._refresh(bufnr, client_id, true) + end + end) api.nvim_buf_attach(bufnr, false, { on_reload = function() @@ -444,21 +441,15 @@ function M._enable(bufnr) end, }) - api.nvim_create_autocmd('LspDetach', { - buf = bufnr, - callback = function(ev) - local clients = lsp.get_clients({ bufnr = bufnr, method = 'textDocument/diagnostic' }) + nvim_on('LspDetach', augroup, { buf = bufnr }, function(ev) + local clients = lsp.get_clients({ bufnr = bufnr, method = 'textDocument/diagnostic' }) - if - not vim.iter(clients):any(function(c) - return c.id ~= ev.data.client_id - end) - then - disable(bufnr) - end - end, - group = augroup, - }) + if not vim.iter(clients):any(function(c) + return c.id ~= ev.data.client_id + end) then + disable(bufnr) + end + end) end --- Returns the result IDs from the reports provided by the given client. diff --git a/runtime/lua/vim/lsp/document_color.lua b/runtime/lua/vim/lsp/document_color.lua index 734218ea94..8f81945e38 100644 --- a/runtime/lua/vim/lsp/document_color.lua +++ b/runtime/lua/vim/lsp/document_color.lua @@ -2,6 +2,7 @@ --- Highlighting is enabled by default. local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local lsp = vim.lsp local util = lsp.util local Capability = require('vim.lsp._capability') @@ -144,19 +145,15 @@ function Provider:new(bufnr) end, }) - api.nvim_create_autocmd('ColorScheme', { - group = self.augroup, - desc = 'Refresh document_color', - callback = function() - color_cache = {} - n_color_cache = 0 - local provider = Provider.active[bufnr] - if provider then - provider:clear() - provider:request() - end - end, - }) + nvim_on('ColorScheme', self.augroup, { desc = 'Refresh document_color' }, function() + color_cache = {} + n_color_cache = 0 + local provider = Provider.active[bufnr] + if provider then + provider:clear() + provider:request() + end + end) return self end diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 49d5f2c4c8..470bbe456f 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -1,5 +1,6 @@ local util = require('vim.lsp.util') local log = require('vim.lsp.log') +local nvim_on = require('vim._core.util').nvim_on local api = vim.api local M = {} @@ -263,55 +264,46 @@ local function _enable(bufnr) refresh(bufnr) end -api.nvim_create_autocmd('LspNotify', { - callback = function(ev) - ---@type integer - local bufnr = ev.buf +nvim_on('LspNotify', augroup, function(ev) + ---@type integer + local bufnr = ev.buf - if ev.data.method ~= 'textDocument/didChange' and ev.data.method ~= 'textDocument/didOpen' then - return - end - if bufstates[bufnr].enabled then - refresh(bufnr, ev.data.client_id) - end - end, - group = augroup, -}) -api.nvim_create_autocmd('LspAttach', { - callback = function(ev) - ---@type integer - local bufnr = ev.buf + if ev.data.method ~= 'textDocument/didChange' and ev.data.method ~= 'textDocument/didOpen' then + return + end + if bufstates[bufnr].enabled then + refresh(bufnr, ev.data.client_id) + end +end) +nvim_on('LspAttach', augroup, function(ev) + ---@type integer + local bufnr = ev.buf - api.nvim_buf_attach(bufnr, false, { - on_reload = function(_, cb_bufnr) - clear(cb_bufnr) - if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then - bufstates[cb_bufnr].applied = {} - refresh(cb_bufnr) - end - end, - on_detach = function(_, cb_bufnr) - _disable(cb_bufnr) - bufstates[cb_bufnr] = nil - end, - }) - end, - group = augroup, -}) -api.nvim_create_autocmd('LspDetach', { - callback = function(ev) - ---@type integer - local bufnr = ev.buf - local clients = vim.lsp.get_clients({ bufnr = bufnr, method = 'textDocument/inlayHint' }) + api.nvim_buf_attach(bufnr, false, { + on_reload = function(_, cb_bufnr) + clear(cb_bufnr) + if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then + bufstates[cb_bufnr].applied = {} + refresh(cb_bufnr) + end + end, + on_detach = function(_, cb_bufnr) + _disable(cb_bufnr) + bufstates[cb_bufnr] = nil + end, + }) +end) +nvim_on('LspDetach', augroup, function(ev) + ---@type integer + local bufnr = ev.buf + local clients = vim.lsp.get_clients({ bufnr = bufnr, method = 'textDocument/inlayHint' }) - if not vim.iter(clients):any(function(c) - return c.id ~= ev.data.client_id - end) then - _disable(bufnr) - end - end, - group = augroup, -}) + if not vim.iter(clients):any(function(c) + return c.id ~= ev.data.client_id + end) then + _disable(bufnr) + end +end) api.nvim_set_decoration_provider(namespace, { on_win = function(_, _, bufnr, topline, botline) ---@type vim.lsp.inlay_hint.bufstate diff --git a/runtime/lua/vim/lsp/inline_completion.lua b/runtime/lua/vim/lsp/inline_completion.lua index 7dae0c717b..76e15658a5 100644 --- a/runtime/lua/vim/lsp/inline_completion.lua +++ b/runtime/lua/vim/lsp/inline_completion.lua @@ -40,6 +40,7 @@ local log = require('vim.lsp.log') local protocol = require('vim.lsp.protocol') local grammar = require('vim.lsp._snippet_grammar') local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local Capability = require('vim.lsp._capability') @@ -78,20 +79,12 @@ Capability.all[Completor.name] = Completor function Completor:new(buf) self = Capability.new(self, buf) self.client_state = {} - api.nvim_create_autocmd({ 'InsertEnter', 'CursorMovedI', 'TextChangedP' }, { - group = self.augroup, - buf = buf, - callback = function() - self:automatic_request() - end, - }) - api.nvim_create_autocmd({ 'InsertLeave' }, { - group = self.augroup, - buf = buf, - callback = function() - self:abort() - end, - }) + nvim_on({ 'InsertEnter', 'CursorMovedI', 'TextChangedP' }, self.augroup, { buf = buf }, function() + self:automatic_request() + end) + nvim_on({ 'InsertLeave' }, self.augroup, { buf = buf }, function() + self:abort() + end) return self end diff --git a/runtime/lua/vim/lsp/linked_editing_range.lua b/runtime/lua/vim/lsp/linked_editing_range.lua index 1a542ddade..b31b3f3e07 100644 --- a/runtime/lua/vim/lsp/linked_editing_range.lua +++ b/runtime/lua/vim/lsp/linked_editing_range.lua @@ -9,6 +9,7 @@ local util = require('vim.lsp.util') local log = require('vim.lsp.log') +local nvim_on = require('vim._core.util').nvim_on local lsp = vim.lsp local method = 'textDocument/linkedEditingRange' local Range = require('vim.treesitter._range') @@ -201,30 +202,18 @@ function LinkedEditor.new(buf) self.augroup = augroup self.client_states = {} - api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { - buf = buf, - group = augroup, - callback = function() - for _, client_state in pairs(self.client_states) do - update_ranges(buf, client_state) - end - self:refresh() - end, - }) - api.nvim_create_autocmd('CursorMoved', { - group = augroup, - buf = buf, - callback = function() - self:refresh() - end, - }) - api.nvim_create_autocmd('LspDetach', { - group = augroup, - buf = buf, - callback = function(ev) - self:detach(ev.data.client_id) - end, - }) + nvim_on({ 'TextChanged', 'TextChangedI' }, augroup, { buf = buf }, function() + for _, client_state in pairs(self.client_states) do + update_ranges(buf, client_state) + end + self:refresh() + end) + nvim_on('CursorMoved', augroup, { buf = buf }, function() + self:refresh() + end) + nvim_on('LspDetach', augroup, { buf = buf }, function(ev) + self:detach(ev.data.client_id) + end) LinkedEditor.active[buf] = self return self @@ -263,20 +252,19 @@ local function detach_linked_editor(bufnr, client) linked_editor:detach(client.id) end -api.nvim_create_autocmd('LspAttach', { +nvim_on('LspAttach', nil, { desc = 'Enable linked editing ranges for all buffers this client attaches to, if enabled', - callback = function(ev) - local client = assert(lsp.get_client_by_id(ev.data.client_id)) - if - not client._enabled_capabilities['linked_editing_range'] - or not client:supports_method(method, ev.buf) - then - return - end +}, function(ev) + local client = assert(lsp.get_client_by_id(ev.data.client_id)) + if + not client._enabled_capabilities['linked_editing_range'] + or not client:supports_method(method, ev.buf) + then + return + end - attach_linked_editor(ev.buf, client) - end, -}) + attach_linked_editor(ev.buf, client) +end) ---@param enable boolean ---@param client vim.lsp.Client @@ -302,16 +290,14 @@ local function toggle_linked_editing_globally(enable) -- If disabling, only clear the attachment autocmd. If enabling, create it. local group = api.nvim_create_augroup('nvim.lsp.linked_editing_range', { clear = true }) if enable then - api.nvim_create_autocmd('LspAttach', { - group = group, + nvim_on('LspAttach', group, { desc = 'Enable linked editing ranges for all clients', - callback = function(ev) - local client = assert(lsp.get_client_by_id(ev.data.client_id)) - if client:supports_method(method, ev.buf) then - attach_linked_editor(ev.buf, client) - end - end, - }) + }, function(ev) + local client = assert(lsp.get_client_by_id(ev.data.client_id)) + if client:supports_method(method, ev.buf) then + attach_linked_editor(ev.buf, client) + end + end) end end diff --git a/runtime/lua/vim/lsp/on_type_formatting.lua b/runtime/lua/vim/lsp/on_type_formatting.lua index 68f1312daf..6ad70bfdab 100644 --- a/runtime/lua/vim/lsp/on_type_formatting.lua +++ b/runtime/lua/vim/lsp/on_type_formatting.lua @@ -1,4 +1,5 @@ local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local lsp = vim.lsp local util = lsp.util local method = 'textDocument/onTypeFormatting' @@ -157,27 +158,24 @@ local function attach(client, bufnr) end api.nvim_clear_autocmds({ group = augroup, buf = bufnr }) - api.nvim_create_autocmd('LspDetach', { + nvim_on('LspDetach', augroup, { buf = bufnr, desc = 'Detach on-type formatting module when the client detaches', - group = augroup, - callback = function(ev) - local detached_client = assert(lsp.get_client_by_id(ev.data.client_id)) - detach(detached_client, bufnr) - end, - }) + }, function(ev) + local detached_client = assert(lsp.get_client_by_id(ev.data.client_id)) + detach(detached_client, bufnr) + end) end -api.nvim_create_autocmd('LspAttach', { +nvim_on('LspAttach', nil, { desc = 'Enable on-type formatting for all buffers with individually-enabled clients.', - callback = function(ev) - local buf = ev.buf - local client = assert(lsp.get_client_by_id(ev.data.client_id)) - if client._otf_enabled then - attach(client, buf) - end - end, -}) +}, function(ev) + local buf = ev.buf + local client = assert(lsp.get_client_by_id(ev.data.client_id)) + if client._otf_enabled then + attach(client, buf) + end +end) ---@param enable boolean ---@param client vim.lsp.Client @@ -203,16 +201,14 @@ local function toggle_globally(enable) -- If disabling, only clear the attachment autocmd. If enabling, create it as well. local group = api.nvim_create_augroup('nvim.lsp.on_type_formatting', { clear = true }) if enable then - api.nvim_create_autocmd('LspAttach', { - group = group, + nvim_on('LspAttach', group, { desc = 'Enable on-type formatting for ALL clients by default.', - callback = function(ev) - local client = assert(lsp.get_client_by_id(ev.data.client_id)) - if client._otf_enabled ~= false then - attach(client, ev.buf) - end - end, - }) + }, function(ev) + local client = assert(lsp.get_client_by_id(ev.data.client_id)) + if client._otf_enabled ~= false then + attach(client, ev.buf) + end + end) end end diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index c1679c1a7c..86400c32f7 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -1,4 +1,5 @@ local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local bit = require('bit') local util = require('vim.lsp.util') local Range = require('vim.treesitter._range') @@ -257,22 +258,14 @@ function STHighlighter:on_attach(client_id) self.client_state[client_id] = state end - api.nvim_create_autocmd({ 'BufWinEnter', 'InsertLeave' }, { - buf = self.bufnr, - group = self.augroup, - callback = function() - self:send_request() - end, - }) + nvim_on({ 'BufWinEnter', 'InsertLeave' }, self.augroup, { buf = self.bufnr }, function() + self:send_request() + end) if state.supports_range then - api.nvim_create_autocmd('WinScrolled', { - buf = self.bufnr, - group = self.augroup, - callback = function() - self:on_change() - end, - }) + nvim_on('WinScrolled', self.augroup, { buf = self.bufnr }, function() + self:on_change() + end) end self:send_request() diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 1687037e6b..111ce83f71 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1,6 +1,7 @@ local protocol = require('vim.lsp.protocol') local validate = vim.validate local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local list_extend = vim.list_extend local uv = vim.uv @@ -1320,28 +1321,20 @@ local function close_preview_autocmd(events, winnr, floating_bufnr, bufnr) -- close the preview window when entered a buffer that is not -- the floating window buffer or the buffer that spawned it - api.nvim_create_autocmd('BufLeave', { - group = augroup, - buf = bufnr, - callback = function() - vim.schedule(function() - -- When jumping to the quickfix window from the preview window, - -- do not close the preview window. - if api.nvim_get_option_value('filetype', { buf = 0 }) ~= 'qf' then - close_preview_window(winnr, { floating_bufnr, bufnr }) - end - end) - end, - }) + nvim_on('BufLeave', augroup, { buf = bufnr }, function() + vim.schedule(function() + -- When jumping to the quickfix window from the preview window, + -- do not close the preview window. + if api.nvim_get_option_value('filetype', { buf = 0 }) ~= 'qf' then + close_preview_window(winnr, { floating_bufnr, bufnr }) + end + end) + end) if #events > 0 then - api.nvim_create_autocmd(events, { - group = augroup, - buf = bufnr, - callback = function() - close_preview_window(winnr) - end, - }) + nvim_on(events, augroup, { buf = bufnr }, function() + close_preview_window(winnr) + end) end end @@ -1593,9 +1586,10 @@ function M.open_floating_preview(contents, syntax, opts) api.nvim_win_set_var(floating_winnr, 'lsp_floating_bufnr', bufnr) end - api.nvim_create_autocmd('WinClosed', { - group = api.nvim_create_augroup('nvim.closing_floating_preview', { clear = true }), - callback = function(args) + nvim_on( + 'WinClosed', + api.nvim_create_augroup('nvim.closing_floating_preview', { clear = true }), + function(args) local winid = vim._tointeger(args.match) local preview_bufnr = vim.w[winid].lsp_floating_bufnr if @@ -1606,8 +1600,8 @@ function M.open_floating_preview(contents, syntax, opts) vim.b[bufnr].lsp_floating_preview = nil return true end - end, - }) + end + ) vim.wo[floating_winnr].foldenable = false -- Disable folding. vim.wo[floating_winnr].wrap = opts.wrap -- Soft wrapping. diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index a4f7389fda..aad11e0915 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -232,6 +232,7 @@ local api = vim.api local uv = vim.uv local async = require('vim._async') local util = require('vim._core.util') +local nvim_on = util.nvim_on local N_ = vim.fn.gettext local M = {} @@ -1199,7 +1200,7 @@ local function show_confirm_buf(lines, on_finish) delete_buffer() end -- - Use `nested` to allow other events (useful for statuslines) - api.nvim_create_autocmd('BufWriteCmd', { buf = bufnr, nested = true, callback = finish }) + nvim_on('BufWriteCmd', nil, { buf = bufnr, nested = true }, finish) -- Define action to cancel confirm --- @type integer @@ -1211,7 +1212,7 @@ local function show_confirm_buf(lines, on_finish) pcall(api.nvim_del_autocmd, cancel_au_id) delete_buffer() end - cancel_au_id = api.nvim_create_autocmd('WinClosed', { nested = true, callback = on_cancel }) + cancel_au_id = nvim_on('WinClosed', nil, { nested = true }, on_cancel) -- Set buffer-local options last (so that user autocmmands could override) vim.bo[bufnr].modified = false diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 5fc7c2ce37..d42e16c44b 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -399,103 +399,97 @@ end --- --- @param bufnr integer local function setup_autocmds(bufnr) - vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, { - group = snippet_group, + local nvim_on = require('vim._core.util').nvim_on + nvim_on({ 'CursorMoved', 'CursorMovedI' }, snippet_group, { desc = 'Update snippet state when the cursor moves', buf = bufnr, - callback = function() - -- Just update the tabstop in insert and select modes. - if not vim.fn.mode():match('^[isS]') then - return - end + }, function() + -- Just update the tabstop in insert and select modes. + if not vim.fn.mode():match('^[isS]') then + return + end - local cursor_row, cursor_col = cursor_pos() + local cursor_row, cursor_col = cursor_pos() - -- The cursor left the snippet region. - local snippet_range = get_extmark_range(bufnr, M._session.extmark_id) - if - cursor_row < snippet_range[1] - or (cursor_row == snippet_range[1] and cursor_col < snippet_range[2]) - or cursor_row > snippet_range[3] - or (cursor_row == snippet_range[3] and cursor_col > snippet_range[4]) - then - M.stop() - return true - end + -- The cursor left the snippet region. + local snippet_range = get_extmark_range(bufnr, M._session.extmark_id) + if + cursor_row < snippet_range[1] + or (cursor_row == snippet_range[1] and cursor_col < snippet_range[2]) + or cursor_row > snippet_range[3] + or (cursor_row == snippet_range[3] and cursor_col > snippet_range[4]) + then + M.stop() + return true + end - for tabstop_index, tabstops in pairs(M._session.tabstops) do - for _, tabstop in ipairs(tabstops) do - local range = tabstop:get_range() - if - (cursor_row > range[1] or (cursor_row == range[1] and cursor_col >= range[2])) - and (cursor_row < range[3] or (cursor_row == range[3] and cursor_col <= range[4])) - then - if tabstop_index ~= 0 then - return - end + for tabstop_index, tabstops in pairs(M._session.tabstops) do + for _, tabstop in ipairs(tabstops) do + local range = tabstop:get_range() + if + (cursor_row > range[1] or (cursor_row == range[1] and cursor_col >= range[2])) + and (cursor_row < range[3] or (cursor_row == range[3] and cursor_col <= range[4])) + then + if tabstop_index ~= 0 then + return end end end + end - -- The cursor is either not on a tabstop or we reached the end, so exit the session. - M.stop() - return true - end, - }) + -- The cursor is either not on a tabstop or we reached the end, so exit the session. + M.stop() + return true + end) - vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI', 'TextChangedP' }, { - group = snippet_group, + nvim_on({ 'TextChanged', 'TextChangedI', 'TextChangedP' }, snippet_group, { desc = 'Update active tabstops when buffer text changes', buf = bufnr, - callback = function() - -- Check that the snippet hasn't been deleted. - local snippet_range = get_extmark_range(M._session.bufnr, M._session.extmark_id) - if - (snippet_range[1] == snippet_range[3] and snippet_range[2] == snippet_range[4]) - or snippet_range[3] + 1 > vim.fn.line('$') - then - M.stop() - end + }, function() + -- Check that the snippet hasn't been deleted. + local snippet_range = get_extmark_range(M._session.bufnr, M._session.extmark_id) + if + (snippet_range[1] == snippet_range[3] and snippet_range[2] == snippet_range[4]) + or snippet_range[3] + 1 > vim.fn.line('$') + then + M.stop() + end - if not M.active() then - return true - end + if not M.active() then + return true + end - -- Sync the tabstops in the current group. - local current_tabstop = M._session.current_tabstop - local current_text = current_tabstop:get_text() - for _, tabstop in ipairs(M._session.tabstops[current_tabstop.index]) do - if tabstop.extmark_id ~= current_tabstop.extmark_id then - tabstop:set_text(current_text) - end + -- Sync the tabstops in the current group. + local current_tabstop = M._session.current_tabstop + local current_text = current_tabstop:get_text() + for _, tabstop in ipairs(M._session.tabstops[current_tabstop.index]) do + if tabstop.extmark_id ~= current_tabstop.extmark_id then + tabstop:set_text(current_text) end - end, - }) + end + end) - vim.api.nvim_create_autocmd('BufLeave', { - group = snippet_group, + nvim_on('BufLeave', snippet_group, { desc = 'Stop the snippet session when leaving the buffer', buf = bufnr, - callback = function() - M.stop() - end, - }) - vim.api.nvim_create_autocmd('ModeChanged', { - group = snippet_group, + }, function() + M.stop() + end) + + nvim_on('ModeChanged', snippet_group, { desc = 'Stop the snippet session when leaving select mode', buf = bufnr, - callback = function(args) - if args.match ~= 's:n' then + }, function(args) + if args.match ~= 's:n' then + return + end + vim.schedule(function() + if not M.active() or vim.api.nvim_get_mode().mode:match('^[siRS\19]') then return end - vim.schedule(function() - if not M.active() or vim.api.nvim_get_mode().mode:match('^[siRS\19]') then - return - end - M.stop() - end) - end, - }) + M.stop() + end) + end) end --- Expands the given snippet text. diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index ce3288af65..6665f5e2dd 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -3,6 +3,7 @@ local ts = vim.treesitter local Range = require('vim.treesitter._range') local api = vim.api +local nvim_on = require('vim._core.util').nvim_on ---Treesitter folding is done in two steps: ---(1) compute the fold levels with the syntax tree and cache the result (`compute_folds_levels`) @@ -226,14 +227,9 @@ function FoldInfo:foldupdate(bufnr, srow, erow) })) > 0 then return end - api.nvim_create_autocmd('InsertLeave', { - group = group, - buf = bufnr, - once = true, - callback = function() - self:do_foldupdate(bufnr) - end, - }) + nvim_on('InsertLeave', group, { buf = bufnr, once = true }, function() + self:do_foldupdate(bufnr) + end) return end @@ -391,13 +387,9 @@ function M.foldexpr(lnum) if not foldinfos[bufnr] then foldinfos[bufnr] = FoldInfo.new(bufnr) - api.nvim_create_autocmd({ 'BufUnload', 'VimEnter', 'FileType' }, { - buf = bufnr, - once = true, - callback = function() - foldinfos[bufnr] = nil - end, - }) + nvim_on({ 'BufUnload', 'VimEnter', 'FileType' }, nil, { buf = bufnr, once = true }, function() + foldinfos[bufnr] = nil + end) local parser = foldinfos[bufnr].parser if not parser then @@ -441,28 +433,27 @@ function M.foldexpr(lnum) return foldinfos[bufnr].levels[lnum] or '0' end -api.nvim_create_autocmd('OptionSet', { +nvim_on('OptionSet', group, { pattern = { 'foldminlines', 'foldnestmax' }, desc = 'Refresh treesitter folds', - callback = function() - local buf = api.nvim_get_current_buf() - local bufs = vim.v.option_type == 'global' and vim.tbl_keys(foldinfos) - or foldinfos[buf] and { buf } - or {} - for _, bufnr in ipairs(bufs) do - local foldinfo = FoldInfo.new(bufnr) - foldinfos[bufnr] = foldinfo - api.nvim_buf_call(bufnr, function() - compute_folds_levels(bufnr, foldinfo, nil, nil, function() - -- FileType/BufUnload can clear or replace the fold state while this - -- async parse is in flight. Ignore callbacks for stale generations. - if foldinfos[bufnr] ~= foldinfo then - return - end - foldinfo:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) - end) +}, function() + local buf = api.nvim_get_current_buf() + local bufs = vim.v.option_type == 'global' and vim.tbl_keys(foldinfos) + or foldinfos[buf] and { buf } + or {} + for _, bufnr in ipairs(bufs) do + local foldinfo = FoldInfo.new(bufnr) + foldinfos[bufnr] = foldinfo + api.nvim_buf_call(bufnr, function() + compute_folds_levels(bufnr, foldinfo, nil, nil, function() + -- FileType/BufUnload can clear or replace the fold state while this + -- async parse is in flight. Ignore callbacks for stale generations. + if foldinfos[bufnr] ~= foldinfo then + return + end + foldinfo:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) end) - end - end, -}) + end) + end +end) return M diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index a1dd988497..64d9a1352e 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -1,4 +1,5 @@ local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local Range = require('vim.treesitter._range') @@ -469,107 +470,83 @@ function M.inspect_tree(opts) local group = api.nvim_create_augroup('nvim.treesitter.dev', {}) - api.nvim_create_autocmd('CursorMoved', { - group = group, - buf = b, - callback = function() - if not api.nvim_buf_is_loaded(buf) then - return true - end - - w = api.nvim_get_current_win() - api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) - local row = api.nvim_win_get_cursor(w)[1] - local lnum, col, end_lnum, end_col = treeview:get(row).node:range() - api.nvim_buf_set_extmark(buf, treeview.ns, lnum, col, { - end_row = end_lnum, - end_col = math.max(0, end_col), - hl_group = 'Visual', - }) - - -- update source window if original was closed - if not api.nvim_win_is_valid(win) then - win = assert(vim.fn.win_findbuf(buf)[1]) - end - - local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) - - -- Move the cursor if highlighted range is completely out of view - if lnum < topline and end_lnum < topline then - api.nvim_win_set_cursor(win, { end_lnum + 1, 0 }) - elseif lnum > botline and end_lnum > botline then - api.nvim_win_set_cursor(win, { lnum + 1, 0 }) - end - end, - }) - - api.nvim_create_autocmd('CursorMoved', { - group = group, - buf = buf, - callback = function() - if not api.nvim_buf_is_loaded(b) then - return true - end - - set_inspector_cursor(treeview, opts.lang, buf, b, w) - end, - }) - - api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { - group = group, - buf = buf, - callback = function() - if not api.nvim_buf_is_loaded(b) then - return true - end - - local treeview_opts = treeview.opts - treeview = assert(TSTreeView:new(buf, opts.lang)) - treeview.opts = treeview_opts - treeview:draw(b) - end, - }) - - api.nvim_create_autocmd('BufLeave', { - group = group, - buf = b, - callback = function() - if not api.nvim_buf_is_loaded(buf) then - return true - end - api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) - end, - }) - - api.nvim_create_autocmd('BufLeave', { - group = group, - buf = buf, - callback = function() - if not api.nvim_buf_is_loaded(b) then - return true - end - api.nvim_buf_clear_namespace(b, treeview.ns, 0, -1) - end, - }) - - api.nvim_create_autocmd({ 'BufHidden', 'BufUnload', 'QuitPre' }, { - group = group, - buf = buf, - callback = function() - -- don't close inpector window if source buffer - -- has more than one open window - if #vim.fn.win_findbuf(buf) > 1 then - return - end - - -- close all tree windows - for _, window in pairs(vim.fn.win_findbuf(b)) do - close_win(window) - end - + nvim_on('CursorMoved', group, { buf = b }, function() + if not api.nvim_buf_is_loaded(buf) then return true - end, - }) + end + + w = api.nvim_get_current_win() + api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) + local row = api.nvim_win_get_cursor(w)[1] + local lnum, col, end_lnum, end_col = treeview:get(row).node:range() + api.nvim_buf_set_extmark(buf, treeview.ns, lnum, col, { + end_row = end_lnum, + end_col = math.max(0, end_col), + hl_group = 'Visual', + }) + + -- update source window if original was closed + if not api.nvim_win_is_valid(win) then + win = assert(vim.fn.win_findbuf(buf)[1]) + end + + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) + + -- Move the cursor if highlighted range is completely out of view + if lnum < topline and end_lnum < topline then + api.nvim_win_set_cursor(win, { end_lnum + 1, 0 }) + elseif lnum > botline and end_lnum > botline then + api.nvim_win_set_cursor(win, { lnum + 1, 0 }) + end + end) + + nvim_on('CursorMoved', group, { buf = buf }, function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + set_inspector_cursor(treeview, opts.lang, buf, b, w) + end) + + nvim_on({ 'TextChanged', 'InsertLeave' }, group, { buf = buf }, function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + local treeview_opts = treeview.opts + treeview = assert(TSTreeView:new(buf, opts.lang)) + treeview.opts = treeview_opts + treeview:draw(b) + end) + + nvim_on('BufLeave', group, { buf = b }, function() + if not api.nvim_buf_is_loaded(buf) then + return true + end + api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) + end) + + nvim_on('BufLeave', group, { buf = buf }, function() + if not api.nvim_buf_is_loaded(b) then + return true + end + api.nvim_buf_clear_namespace(b, treeview.ns, 0, -1) + end) + + nvim_on({ 'BufHidden', 'BufUnload', 'QuitPre' }, group, { buf = buf }, function() + -- don't close inpector window if source buffer + -- has more than one open window + if #vim.fn.win_findbuf(buf) > 1 then + return + end + + -- close all tree windows + for _, window in pairs(vim.fn.win_findbuf(b)) do + close_win(window) + end + + return true + end) end local edit_ns = api.nvim_create_namespace('nvim.treesitter.dev_edit') @@ -671,53 +648,43 @@ function M.edit_query(lang) api.nvim_buf_set_name(query_buf, string.format('%s/query_editor.scm', lang)) local group = api.nvim_create_augroup('nvim.treesitter.dev_edit', {}) - api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { - group = group, + nvim_on({ 'TextChanged', 'InsertLeave' }, group, { buf = query_buf, desc = 'Update query editor diagnostics when the query changes', - callback = function() - vim.treesitter.query.lint(query_buf, { langs = lang, clear = false }) - end, - }) - api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave', 'CursorMoved', 'BufEnter' }, { - group = group, + }, function() + vim.treesitter.query.lint(query_buf, { langs = lang, clear = false }) + end) + nvim_on({ 'TextChanged', 'InsertLeave', 'CursorMoved', 'BufEnter' }, group, { buf = query_buf, desc = 'Update query editor highlights when the cursor moves', - callback = function() - if api.nvim_win_is_valid(win) then - update_editor_highlights(query_win, win, lang) - end - end, - }) - api.nvim_create_autocmd('BufLeave', { - group = group, + }, function() + if api.nvim_win_is_valid(win) then + update_editor_highlights(query_win, win, lang) + end + end) + nvim_on('BufLeave', group, { buf = query_buf, desc = 'Clear highlights when leaving the query editor', - callback = function() - api.nvim_buf_clear_namespace(buf, edit_ns, 0, -1) - end, - }) - api.nvim_create_autocmd('BufLeave', { - group = group, + }, function() + api.nvim_buf_clear_namespace(buf, edit_ns, 0, -1) + end) + nvim_on('BufLeave', group, { buf = buf, desc = 'Clear the query editor highlights when leaving the source buffer', - callback = function() - if not api.nvim_buf_is_loaded(query_buf) then - return true - end + }, function() + if not api.nvim_buf_is_loaded(query_buf) then + return true + end - api.nvim_buf_clear_namespace(query_buf, edit_ns, 0, -1) - end, - }) - api.nvim_create_autocmd({ 'BufHidden', 'BufUnload' }, { - group = group, + api.nvim_buf_clear_namespace(query_buf, edit_ns, 0, -1) + end) + nvim_on({ 'BufHidden', 'BufUnload' }, group, { buf = buf, desc = 'Close the editor window when the source buffer is hidden or unloaded', once = true, - callback = function() - close_win(query_win) - end, - }) + }, function() + close_win(query_win) + end) api.nvim_buf_set_lines(query_buf, 0, -1, false, { ';; Write queries here (see $VIMRUNTIME/queries/ for examples).', diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 861cefd3aa..a615d93500 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -2,6 +2,7 @@ --- text. See |vim.treesitter.query.parse()| for a working example. local api = vim.api +local nvim_on = require('vim._core.util').nvim_on local language = require('vim.treesitter.language') local memoize = vim.func._memoize local cmp_ge = require('vim.treesitter._range').cmp_pos.ge @@ -333,14 +334,17 @@ M.get = memoize('concat-2', function(lang, query_name) return M.parse(lang, query_string) end, false) -api.nvim_create_autocmd('OptionSet', { - pattern = { 'runtimepath' }, - group = api.nvim_create_augroup('nvim.treesitter.query_cache_reset', { clear = true }), - callback = function() +nvim_on( + 'OptionSet', + api.nvim_create_augroup('nvim.treesitter.query_cache_reset', { clear = true }), + { + pattern = { 'runtimepath' }, + }, + function() --- @diagnostic disable-next-line: undefined-field LuaLS bad at generics M.get:clear() - end, -}) + end +) --- Parses a {query} string and returns a `Query` object (|lua-treesitter-query|), which can be used --- to search the tree for the query patterns (via |Query:iter_captures()|, |Query:iter_matches()|), diff --git a/runtime/lua/vim/tty.lua b/runtime/lua/vim/tty.lua index 2693c1bc97..6771efe849 100644 --- a/runtime/lua/vim/tty.lua +++ b/runtime/lua/vim/tty.lua @@ -28,18 +28,19 @@ function M.request(payload, opts, on_response) timer = assert(vim.uv.new_timer()) end - local id = vim.api.nvim_create_autocmd('TermResponse', { - group = opts.group, - nested = true, - callback = function(ev) + local id = require('vim._core.util').nvim_on( + 'TermResponse', + opts.group, + { nested = true }, + function(ev) local stop = on_response(ev.data.sequence) -- If on_response is done, cancel the timeout so on_timeout doesn't fire spuriously. if stop and timer and not timer:is_closing() then timer:close() end return stop - end, - }) + end + ) if payload ~= '' then vim.api.nvim_ui_send(payload) diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index fd065d1ad5..c34c28f23a 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -342,11 +342,13 @@ do --- Initialize Progress handlers. local function progress_init() progress_group = vim.api.nvim_create_augroup('nvim.ui.progress_status', { clear = true }) - progress_autocmd = vim.api.nvim_create_autocmd('Progress', { - group = progress_group, - desc = 'Tracks progress messages for vim.ui.progress_status()', - ---@param ev {data: vim.event.progress.data} - callback = function(ev) + progress_autocmd = require('vim._core.util').nvim_on( + 'Progress', + progress_group, + { + desc = 'Tracks progress messages for vim.ui.progress_status()', + }, ---@param ev {data: vim.event.progress.data} + function(ev) if not ev.data or not ev.data.id then return end @@ -362,8 +364,8 @@ do then progress[ev.data.id] = nil end - end, - }) + end + ) end --- Gets a status description summarizing currently running progress messages. diff --git a/runtime/lua/vim/ui/img.lua b/runtime/lua/vim/ui/img.lua index 586a5dff82..129917a973 100644 --- a/runtime/lua/vim/ui/img.lua +++ b/runtime/lua/vim/ui/img.lua @@ -1,3 +1,5 @@ +local nvim_on = require('vim._core.util').nvim_on + local M = {} ---@brief @@ -142,10 +144,8 @@ function M._supported(opts) return require('vim.ui.img._kitty').supported(opts) end -vim.api.nvim_create_autocmd('VimLeavePre', { - callback = function() - M.del(math.huge) - end, -}) +nvim_on('VimLeavePre', nil, function() + M.del(math.huge) +end) return M diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index e7b05c8234..bc7e2dcf80 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -324,13 +324,10 @@ describe('startup', function() os.remove('Xtest_shada') end) - assert_l_out( - 'updatecount=0 shadafile=NONE loadplugins=false scripts=1\n', - nil, - nil, - '-', - script - ) + assert_l_out(function(out) + -- Accept scripts=2 for PUC Lua where `vim._core.util` is sourced from disk instead of a preload blob. + return matches('updatecount=0 shadafile=NONE loadplugins=false scripts=[12]\n', out) + end, nil, nil, '-', script) -- User can override. assert_l_out(