diff --git a/.github/scripts/labeler_configuration.yml b/.github/scripts/labeler_configuration.yml index 933e0bcbbd..b548d96afc 100644 --- a/.github/scripts/labeler_configuration.yml +++ b/.github/scripts/labeler_configuration.yml @@ -46,6 +46,10 @@ folds: - changed-files: - any-glob-to-any-file: [ src/nvim/fold* ] +image: + - changed-files: + - any-glob-to-any-file: [ '**/ui/img*' ] + lsp: - changed-files: - any-glob-to-any-file: [ runtime/lua/vim/lsp.lua, runtime/lua/vim/lsp/* ] diff --git a/.github/workflows/labeler_issue.yml b/.github/workflows/labeler_issue.yml index e448c604e9..f145a9894a 100644 --- a/.github/workflows/labeler_issue.yml +++ b/.github/workflows/labeler_issue.yml @@ -17,7 +17,14 @@ jobs: script: | const title = context.payload.issue.title; const titleSplit = title.split(/\b/).map(e => e.toLowerCase()); - const keywords = ['api', 'lsp', 'treesitter', 'ui', 'ui2']; + const keywords = [ + 'api', + 'image', + 'lsp', + 'treesitter', + 'ui', + 'ui2' + ]; var labels = new Set(); for (const keyword of keywords) { diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 8c8aceea4b..5c171cff57 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -154,7 +154,7 @@ indices, end-inclusive): - |nvim_buf_get_extmarks()| - |nvim_buf_set_extmark()| - *api-fast* *schedule* + *api-fast* *deferred* *schedule* Most API functions are deferred: they are queued ("scheduled") on the main loop and processed sequentially with normal input. If the editor is waiting for user input in a "modal" fashion (e.g. an |input()| prompt), a deferred diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index fc1e8a3fb7..4c9f02de89 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -922,9 +922,8 @@ OptionSet After setting an option (except during always use |:noautocmd| to prevent triggering OptionSet. - Note: The 'modified' option triggers - |OptionSet| deferred to the next event-loop - tick, even when set via |:set|. + Note: OptionSet is |deferred| when triggered by + 'modified', even when set via |:set|. Non-recursive: |:set| in the autocommand does not trigger OptionSet again. diff --git a/runtime/doc/dev_vimpatch.txt b/runtime/doc/dev_vimpatch.txt index f6b7cffe69..e696416758 100644 --- a/runtime/doc/dev_vimpatch.txt +++ b/runtime/doc/dev_vimpatch.txt @@ -6,15 +6,13 @@ Merging patches from Vim *dev-vimpatch* Nvim was forked from Vim 7.4.160; it is kept up-to-date with relevant Vim -patches in order to avoid duplicate work. Run `vim-patch.sh` -https://github.com/neovim/neovim/blob/master/scripts/vim-patch.sh to see the -status of Vim patches: ->bash +patches in order to avoid duplicate work. Run `vim-patch.sh` to see the status +of Vim patches: >bash ./scripts/vim-patch.sh -l < -Everyone is welcome to |dev-vimpatch-pull-requests| for relevant Vim -patches, but some types of patches are |dev-vimpatch-not-applicable|. -See |dev-vimpatch-quickstart| to get started immediately. +Everyone is welcome to |dev-vimpatch-pull-requests| for relevant Vim patches, +but some types of patches are |dev-vimpatch-not-applicable|. See +|dev-vimpatch-quickstart| to get started immediately. Type |gO| to see the table of contents. diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 2453fe605f..b21f072e33 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -47,6 +47,15 @@ can connect to any Nvim instance). Example: this sets "g:gui" to the value of the UI's "rgb" field: > :autocmd UIEnter * let g:gui = filter(nvim_list_uis(),{k,v-> v.chan==v:event.chan})[0].rgb < + *ui-lifecycle* +Nvim's |clientserver| architecture allows you to |:restart|, |:detach| (Nvim +continues in the background), or |:connect| to another instance. + +Use cases: +- keep the session running on remote machines after (unexpected) SSH disconnect +- open files in an existing session from your OS file explorer +- run Nvim as a background process (daemon/service), atttach/detach as needed + ------------------------------------------------------------------------------ Stop or detach the current UI diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 8f48305cb5..2cf17325ca 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -85,7 +85,6 @@ EVENTS • |BufModifiedSet| has been removed. Use the |OptionSet| event with pattern "modified" instead. -• todo LSP @@ -93,7 +92,7 @@ LSP LUA -• Added `__eq` metamethod to |vim.VersionRange|. +• todo OPTIONS @@ -225,7 +224,7 @@ UI VIMSCRIPT • |v:exitreason| is set before |QuitPre|. -• |v:starttime| is the process start time (nanoseconds from UNIX epoch). +• |v:starttime| is the process start time (nanoseconds since UNIX epoch). ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 41ce83bd6a..e104336bca 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -334,8 +334,8 @@ Editor: Events (autocommands): - Fixed inconsistent behavior in execution of nested autocommands #23368 -- |OptionSet| is now fired for 'modified' when writing a file, - undoing changes, or using |:set| modified. +- |OptionSet| is triggered for 'modified' when writing a file, undoing changes, + or using |:set| modified. - |Progress| - |RecordingEnter| - |RecordingLeave| diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 5d7efac2c8..e61bc78624 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -635,7 +635,7 @@ v:stacktrace *v:starttime* *starttime-variable* v:starttime - Timestamp (nanoseconds from UNIX epoch) when the Nvim process + Timestamp (nanoseconds since UNIX epoch) when the Nvim process started. To see the current "uptime": >lua diff --git a/runtime/lua/vim/_meta/vvars.gen.lua b/runtime/lua/vim/_meta/vvars.gen.lua index bee771bc23..543d4169c1 100644 --- a/runtime/lua/vim/_meta/vvars.gen.lua +++ b/runtime/lua/vim/_meta/vvars.gen.lua @@ -667,7 +667,7 @@ vim.v.shell_error = ... --- @type table[] vim.v.stacktrace = ... ---- Timestamp (nanoseconds from UNIX epoch) when the Nvim process +--- Timestamp (nanoseconds since UNIX epoch) when the Nvim process --- started. --- --- To see the current "uptime": diff --git a/runtime/lua/vim/lsp/inline_completion.lua b/runtime/lua/vim/lsp/inline_completion.lua index 5bf4ba920e..e759562747 100644 --- a/runtime/lua/vim/lsp/inline_completion.lua +++ b/runtime/lua/vim/lsp/inline_completion.lua @@ -73,21 +73,21 @@ setmetatable(Completor, Capability) Capability.all[Completor.name] = Completor ---@package ----@param bufnr integer +---@param buf integer ---@return vim.lsp.inline_completion.Completor -function Completor:new(bufnr) - self = Capability.new(self, bufnr) +function Completor:new(buf) + self = Capability.new(self, buf) self.client_state = {} api.nvim_create_autocmd({ 'InsertEnter', 'CursorMovedI', 'TextChangedP' }, { group = self.augroup, - buf = bufnr, + buf = buf, callback = function() self:automatic_request() end, }) api.nvim_create_autocmd({ 'InsertLeave' }, { group = self.augroup, - buf = bufnr, + buf = buf, callback = function() self:abort() end, diff --git a/runtime/lua/vim/lsp/linked_editing_range.lua b/runtime/lua/vim/lsp/linked_editing_range.lua index 07cc6fb740..1a542ddade 100644 --- a/runtime/lua/vim/lsp/linked_editing_range.lua +++ b/runtime/lua/vim/lsp/linked_editing_range.lua @@ -191,43 +191,42 @@ end ---Construct a new LinkedEditor for the buffer. --- ---@private ----@param bufnr integer +---@param buf integer ---@return vim.lsp.linked_editing_range.LinkedEditor -function LinkedEditor.new(bufnr) +function LinkedEditor.new(buf) local self = setmetatable({}, { __index = LinkedEditor }) - self.bufnr = bufnr - local augroup = - api.nvim_create_augroup('nvim.lsp.linked_editing_range:' .. bufnr, { clear = true }) + self.bufnr = buf + local augroup = api.nvim_create_augroup('nvim.lsp.linked_editing_range:' .. buf, { clear = true }) self.augroup = augroup self.client_states = {} api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { - buf = bufnr, + buf = buf, group = augroup, callback = function() for _, client_state in pairs(self.client_states) do - update_ranges(bufnr, client_state) + update_ranges(buf, client_state) end self:refresh() end, }) api.nvim_create_autocmd('CursorMoved', { group = augroup, - buf = bufnr, + buf = buf, callback = function() self:refresh() end, }) api.nvim_create_autocmd('LspDetach', { group = augroup, - buf = bufnr, + buf = buf, callback = function(ev) self:detach(ev.data.client_id) end, }) - LinkedEditor.active[bufnr] = self + LinkedEditor.active[buf] = self return self end diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 6739073cd3..49d125826b 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -191,7 +191,7 @@ local default_dispatchers = { } --- @async -local function parse_content_length() +local function message_decoder() local strbuf = strbuffer.new() while true do local header_len ---@type integer? @@ -226,7 +226,7 @@ end function M.create_read_loop(handle_body, on_exit, on_error) on_exit = on_exit or function() end on_error = on_error or function() end - local co = coroutine.create(parse_content_length) + local co = coroutine.create(message_decoder) coroutine.resume(co) return function(err, chunk) if err then diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 30f89ac129..7a9ebf85b0 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -923,8 +923,8 @@ function M.make_floating_popup_options(width, height, opts) local anchor = '' - local lines_above --- @type integer - local lines_below --- @type integer + local lines_above = vim.fn.winline() - 1 + local lines_below = vim.fn.winheight(0) - lines_above if opts.relative == 'mouse' then lines_above = vim.fn.getmousepos().line - 1 lines_below = vim.fn.winheight(0) - lines_above @@ -932,9 +932,6 @@ function M.make_floating_popup_options(width, height, opts) -- No cursor to anchor against; treat the whole editor as space below. lines_above = 0 lines_below = vim.o.lines - else - lines_above = vim.fn.winline() - 1 - lines_below = vim.fn.winheight(0) - lines_above end local anchor_bias = opts.anchor_bias or 'auto' @@ -962,13 +959,11 @@ function M.make_floating_popup_options(width, height, opts) row = 0 end - local wincol --- @type integer + local wincol = vim.fn.wincol() if opts.relative == 'mouse' then wincol = vim.fn.getmousepos().column elseif opts.relative == 'editor' then wincol = 0 - else - wincol = vim.fn.wincol() end if wincol + width + (opts.offset_x or 0) <= vim.o.columns then diff --git a/src/gen/lint.lua b/src/gen/lint.lua index ee7a61456f..442e5c2b9a 100644 --- a/src/gen/lint.lua +++ b/src/gen/lint.lua @@ -6,6 +6,7 @@ local M = {} --- Apply to parameter names, keyset keys, and function name parts. local banned_nouns = { buffer = 'buf', + bufnr = 'buf', -- channel = 'chan', command = 'cmd', directory = 'dir', @@ -13,6 +14,7 @@ local banned_nouns = { position = 'pos', process = 'proc', window = 'win', + winnr = 'win', } --- Banned verbs. See `:help dev-name-common`. @@ -55,31 +57,140 @@ local legacy_names = { nvim_list_uis = true, nvim_list_wins = true, }, + ['runtime/lua/vim/diagnostic.lua'] = { + count = true, + get = true, + hide = true, + reset = true, + set = true, + show = true, + status = true, + }, + ['runtime/lua/editorconfig.lua'] = { + config = true, + }, + ['runtime/lua/vim/uri.lua'] = { + uri_from_bufnr = true, + uri_to_bufnr = true, + }, + ['runtime/lua/vim/snippet.lua'] = { + new = true, + }, + ['runtime/lua/vim/hl.lua'] = { + range = true, + }, + ['runtime/lua/vim/filetype.lua'] = { + _getline = true, + _getlines = true, + _nextnonblank = true, + }, + ['runtime/lua/vim/_meta/regex.lua'] = { + match_line = true, + }, + ['runtime/lua/vim/_inspector.lua'] = { + ['vim.inspect_pos'] = true, + ['vim.show_pos'] = true, + }, ['runtime/lua/vim/_core/shared.lua'] = { _ensure_list = true, _list_insert = true, _list_remove = true, + _resolve_bufnr = true, list_contains = true, tbl_contains = true, }, ['runtime/lua/vim/lsp.lua'] = { + _buf_get_full_text = true, + _buf_get_line_ending = true, + _set_defaults = true, + buf_attach_client = true, + buf_detach_client = true, + buf_is_attached = true, buf_notify = true, + buf_request = true, + buf_request_all = true, + buf_request_sync = true, + }, + ['runtime/lua/vim/lsp/_folding_range.lua'] = { + new = true, + }, + ['runtime/lua/vim/lsp/_changetracking.lua'] = { + _send_did_save = true, + flush = true, + init = true, + reset_buf = true, + send_changes = true, + }, + ['runtime/lua/vim/lsp/_capability.lua'] = { + new = true, + }, + ['runtime/lua/vim/lsp/semantic_tokens.lua'] = { + _start = true, + force_refresh = true, + get_at_pos = true, + highlight_token = true, + new = true, + }, + ['runtime/lua/vim/lsp/document_color.lua'] = { + new = true, + }, + ['runtime/lua/vim/lsp/diagnostic.lua'] = { + _enable = true, + _refresh = true, + get_line_diagnostics = true, + }, + ['runtime/lua/vim/lsp/completion.lua'] = { + enable = true, + request = true, + }, + ['runtime/lua/vim/lsp/codelens.lua'] = { + new = true, }, ['runtime/lua/vim/lsp/client.lua'] = { + _get_registrations = true, + _on_detach = true, _on_exit = true, _process_request = true, _process_static_registrations = true, _remove_workspace_folder = true, + _text_document_did_open_handler = true, + on_attach = true, + request = true, + request_sync = true, + supports_method = true, }, ['runtime/lua/vim/lsp/util.lua'] = { + _make_line_range_params = true, + apply_text_edits = true, + buf_clear_references = true, buf_highlight_references = true, + get_effective_tabstop = true, + make_given_range_params = true, make_position_params = true, + make_text_document_params = true, + symbols_to_items = true, }, ['runtime/lua/vim/lsp/rpc.lua'] = { _notify = true, }, ['runtime/lua/vim/treesitter.lua'] = { + _create_parser = true, + get_captures_at_cursor = true, + get_captures_at_pos = true, + get_parser = true, node_contains = true, + start = true, + stop = true, + }, + ['runtime/lua/vim/treesitter/languagetree.lua'] = { + _on_bytes = true, + }, + ['runtime/lua/vim/treesitter/dev.lua'] = { + draw = true, + new = true, + }, + ['runtime/lua/vim/treesitter/_fold.lua'] = { + new = true, }, ['runtime/lua/vim/treesitter/highlighter.lua'] = { for_each_highlight_state = true, @@ -105,10 +216,33 @@ local legacy_names = { --- --- @type table> local legacy_fields = { + ['vim.Diagnostic'] = { + bufnr = true, + }, + ['vim.lsp.Capability'] = { + bufnr = true, + }, + ['vim.lsp.inlay_hint.enable.Filter'] = { + bufnr = true, + }, + ['vim.lsp.inlay_hint.get.Filter'] = { + bufnr = true, + }, + ['vim.lsp.inlay_hint.get.ret'] = { + bufnr = true, + }, + ['TS.Heading'] = { + bufnr = true, + }, + ['vim.treesitter.get_node.Opts'] = { + bufnr = true, + }, ['vim.treesitter.dev.inspect_tree.Opts'] = { + bufnr = true, command = true, }, ['vim.undotree.opts'] = { + bufnr = true, command = true, }, } @@ -148,12 +282,14 @@ function M.lint_names(source, api_funs, keysets, classes) local src_legacy = legacy_names[source] or {} for _, fun in ipairs(api_funs) do if fun.name and fun.params and not fun.deprecated and not fun.deprecated_since then - -- Positional parameter names: always checked (no "legacy" allowed). - for _, p in ipairs(fun.params) do - local want_name = banned_nouns[p.name] - if want_name then - local msg = '%s: %s(): param "%s" should be renamed to "%s"' - errors[#errors + 1] = fmt(msg, source, fun.name, p.name, want_name) + -- Positional parameter names. + if not src_legacy[fun.name] then + for _, p in ipairs(fun.params) do + local want_name = banned_nouns[p.name] + if want_name then + local msg = '%s: %s(): param "%s" should be renamed to "%s"' + errors[#errors + 1] = fmt(msg, source, fun.name, p.name, want_name) + end end end diff --git a/src/nvim/option.c b/src/nvim/option.c index 76355c783b..23907085a2 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2060,7 +2060,7 @@ void set_option_sctx(OptIndex opt_idx, int opt_flags, sctx_T script_ctx) } } -/// Fire OptionSet autocmd directly (called from deferred context, bypasses defer logic). +/// Execute OptionSet autocmd now (not deferred). void apply_optionset_autocmd_now(OptIndex opt_idx, int opt_flags, OptVal oldval, OptVal oldval_g, OptVal oldval_l, OptVal newval, const char *errmsg) { diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index 7096349125..db87bcda28 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -756,7 +756,7 @@ M.vars = { starttime = { type = 'integer', desc = [=[ - Timestamp (nanoseconds from UNIX epoch) when the Nvim process + Timestamp (nanoseconds since UNIX epoch) when the Nvim process started. To see the current "uptime": >lua diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index fde6440d50..487352ce7a 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -857,52 +857,4 @@ describe('autocmd', function() fn.execute('autocmd User ,,,there,is,,a,fly,,') ) end) - - describe('OptionSet modified', function() - before_each(clear) - - it('is triggered when modified and un-modified', function() - source([[ - let g:modified = 0 - autocmd OptionSet modified let g:modified += 1 - ]]) - request('nvim_command', [[normal! aa\]]) - eq(1, eval('g:modified')) - request('nvim_command', [[normal! u]]) - eq(2, eval('g:modified')) - end) - - it('triggers when writes non-current buffer #32817', function() - source([[ - let g:modified = 0 - let g:second_trigger_buf = 0 - autocmd OptionSet modified let g:modified += 1 | if g:modified == 2 | let g:second_trigger_buf = bufnr() | endif - ]]) - request('nvim_command', [[edit test_a | badd test_b]]) - request('nvim_command', [[normal! aa\]]) - request('nvim_command', [[let g:buf_a = bufnr()]]) - request('nvim_command', [[bn]]) - request('nvim_command', [[wa]]) - os.remove('test_a') - eq({ 2, true }, { eval('g:modified'), eval('g:buf_a') == eval('g:second_trigger_buf') }) - end) - - it('OptionSet triggers correctly when modified changes', function() - command([[ - autocmd OptionSet modified call add(g:messages, string(!!v:option_old) . ' -> ' . string(!!v:option_new) . ' - actual: ' . &modified) - ]]) - command('let g:messages = []') - local fname = t.tmpname() - command('new ' .. fname) - command("call setline(1, 'hi')") - command('write') - command('set modified') - eq({ - '0 -> 1 - actual: 1', - '1 -> 0 - actual: 0', - '0 -> 1 - actual: 1', - }, eval('g:messages')) - os.remove(fname) - end) - end) end) diff --git a/test/functional/autocmd/optionset_spec.lua b/test/functional/autocmd/optionset_spec.lua new file mode 100644 index 0000000000..3bfc7b89fc --- /dev/null +++ b/test/functional/autocmd/optionset_spec.lua @@ -0,0 +1,59 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local eq = t.eq +local eval = n.eval +local clear = n.clear +local command = n.command +local source = n.source +local request = n.request + +describe('autocmd', function() + describe('OptionSet modified', function() + before_each(clear) + + it('is triggered when modified and un-modified', function() + source([[ + let g:modified = 0 + autocmd OptionSet modified let g:modified += 1 + ]]) + request('nvim_command', [[normal! aa\]]) + eq(1, eval('g:modified')) + request('nvim_command', [[normal! u]]) + eq(2, eval('g:modified')) + end) + + it('triggers when writes non-current buffer #32817', function() + source([[ + let g:modified = 0 + let g:second_trigger_buf = 0 + autocmd OptionSet modified let g:modified += 1 | if g:modified == 2 | let g:second_trigger_buf = bufnr() | endif + ]]) + request('nvim_command', [[edit test_a | badd test_b]]) + request('nvim_command', [[normal! aa\]]) + request('nvim_command', [[let g:buf_a = bufnr()]]) + request('nvim_command', [[bn]]) + request('nvim_command', [[wa]]) + os.remove('test_a') + eq({ 2, true }, { eval('g:modified'), eval('g:buf_a') == eval('g:second_trigger_buf') }) + end) + + it('OptionSet triggers correctly when modified changes', function() + command([[ + autocmd OptionSet modified call add(g:messages, string(!!v:option_old) . ' -> ' . string(!!v:option_new) . ' - actual: ' . &modified) + ]]) + command('let g:messages = []') + local fname = t.tmpname() + command('new ' .. fname) + command("call setline(1, 'hi')") + command('write') + command('set modified') + eq({ + '0 -> 1 - actual: 1', + '1 -> 0 - actual: 0', + '0 -> 1 - actual: 1', + }, eval('g:messages')) + os.remove(fname) + end) + end) +end)