From 822778f7e56bd6340a5d590056f35e93e4a02fa1 Mon Sep 17 00:00:00 2001 From: Till Bungert Date: Tue, 28 Apr 2026 01:01:47 +0200 Subject: [PATCH 1/2] fix(excmd): use realtime for v:starttime, :uptime #39425 Problem: `v:starttime`, `:uptime` use a monotonic high-resolution timer. This only works as long as the timer keeps running (if the computer is suspended the timer is paused). This is somewhat unintuitive, and doesn't match the behavior of the `uptime` shell command. Solution: Implement `os_realtime` to get the real time since the epoch in nanoseconds. --- runtime/doc/news.txt | 2 +- runtime/doc/vvars.txt | 4 ++-- runtime/lua/vim/_meta/vvars.gen.lua | 4 ++-- src/nvim/main.c | 2 +- src/nvim/os/time.c | 19 +++++++++++++++++++ src/nvim/vvars.lua | 4 ++-- 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 1b9f2be9e7..8ef8161f8b 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -442,7 +442,7 @@ VIMSCRIPT • |v:vim_did_init| is set after sourcing |init.vim| but before |load-plugins|. • |prompt_appendbuf()| appends text to prompt-buffer. • |v:exitreason| is set before |QuitPre|. -• |v:starttime| is the process start time (monotonic nanoseconds). +• |v:starttime| is the process start time (nanoseconds from UNIX epoch). ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 6a885f6a4f..5d7efac2c8 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -635,11 +635,11 @@ v:stacktrace *v:starttime* *starttime-variable* v:starttime - Timestamp (monotonic nanoseconds) when the Nvim process + Timestamp (nanoseconds from UNIX epoch) when the Nvim process started. To see the current "uptime": >lua - vim.print(('uptime: %d seconds'):format((vim.uv.hrtime() - vim.v.starttime) / 1e9)) + vim.print(('uptime: %d seconds'):format(os.time() - (vim.v.starttime / 1e9))) < Read-only. diff --git a/runtime/lua/vim/_meta/vvars.gen.lua b/runtime/lua/vim/_meta/vvars.gen.lua index 7b5ca0e417..bee771bc23 100644 --- a/runtime/lua/vim/_meta/vvars.gen.lua +++ b/runtime/lua/vim/_meta/vvars.gen.lua @@ -667,13 +667,13 @@ vim.v.shell_error = ... --- @type table[] vim.v.stacktrace = ... ---- Timestamp (monotonic nanoseconds) when the Nvim process +--- Timestamp (nanoseconds from UNIX epoch) when the Nvim process --- started. --- --- To see the current "uptime": --- --- ```lua ---- vim.print(('uptime: %d seconds'):format((vim.uv.hrtime() - vim.v.starttime) / 1e9)) +--- vim.print(('uptime: %d seconds'):format(os.time() - (vim.v.starttime / 1e9))) --- ``` --- --- Read-only. diff --git a/src/nvim/main.c b/src/nvim/main.c index 02cfc59849..96696f65ae 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -197,7 +197,7 @@ void early_init(mparm_T *paramp) estack_init(); cmdline_init(); eval_init(); // init global variables - set_vim_var_nr(VV_STARTTIME, (varnumber_T)os_hrtime()); + set_vim_var_nr(VV_STARTTIME, (varnumber_T)os_realtime()); init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. runtime_init(); diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index f243eda047..2e6bf3e56d 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -32,6 +32,25 @@ uint64_t os_hrtime(void) return uv_hrtime(); } +/// Obtain the current system time from a high-resolution +/// real-time clock source. +/// +/// The real-time clock counts from the UNIX epoch (1970-01-01) +/// and is subject to time adjustments; it can jump back in time. +/// +/// @return Nanoseconds since epoch or 0 +int64_t os_realtime(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + uv_timespec64_t ts = { 0 }; + int error_number; + if ((error_number = uv_clock_gettime(UV_CLOCK_REALTIME, &ts)) != 0) { + ELOG("uv_clock_gettime failed: %d %s", error_number, uv_err_name(error_number)); + return 0; + } + return ts.tv_sec * 1000000000L + ts.tv_nsec; +} + /// Gets a millisecond-resolution, monotonically-increasing time relative to an /// arbitrary time in the past. /// diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index 648e5f1310..7096349125 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -756,11 +756,11 @@ M.vars = { starttime = { type = 'integer', desc = [=[ - Timestamp (monotonic nanoseconds) when the Nvim process + Timestamp (nanoseconds from UNIX epoch) when the Nvim process started. To see the current "uptime": >lua - vim.print(('uptime: %d seconds'):format((vim.uv.hrtime() - vim.v.starttime) / 1e9)) + vim.print(('uptime: %d seconds'):format(os.time() - (vim.v.starttime / 1e9))) < Read-only. ]=], From 4ee47a56ec936e292b23bafbd074bac0e18a9822 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 29 Apr 2026 17:37:47 -0400 Subject: [PATCH 2/2] docs: misc --- .github/scripts/labeler_configuration.yml | 4 + .github/workflows/labeler_issue.yml | 9 +- runtime/doc/api.txt | 2 +- runtime/doc/dev_vimpatch.txt | 12 +- runtime/doc/gui.txt | 9 ++ runtime/doc/news.txt | 2 +- runtime/doc/vvars.txt | 2 +- runtime/lua/vim/_meta/vvars.gen.lua | 2 +- runtime/lua/vim/lsp/inline_completion.lua | 10 +- runtime/lua/vim/lsp/linked_editing_range.lua | 19 ++- src/gen/lint.lua | 148 ++++++++++++++++++- src/nvim/vvars.lua | 2 +- 12 files changed, 187 insertions(+), 34 deletions(-) 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 02e5a6e3b1..0c49689b66 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 c8b99e3838..a7dc0d556d 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/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 8ef8161f8b..0cce01eef6 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -442,7 +442,7 @@ VIMSCRIPT • |v:vim_did_init| is set after sourcing |init.vim| but before |load-plugins|. • |prompt_appendbuf()| appends text to prompt-buffer. • |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/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/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/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