ci(lua): enable annotation and style checks

Problem:

- annotation-usage-error and code-style-check are disabled.
- vim.inspect relies on a dummy self-assignment for docgen, and its help
  omits parameter and option details.
- LuaLS fallback types obscure iterator inference.
- Docgen hardcodes public function names and appends separately sorted
  files, leaving shared helpers under the inspector heading.

Solution:

- Enable annotation-usage-error and code-style-check, and fix misplaced
  annotations.
- Document vim.inspect's callable module directly, removing the dummy
  assignment. Describe its parameters, option defaults and process
  callback.
- Remove LuaLS iterator fallbacks, correct iterator signatures and check
  values that the improved inference identifies as optional.
- Parse callable modules, require bindings and parent module references.
  Derive public names and inherited function docs from these links,
  including missing help for vim.async.semaphore() and vim.version().
- Export vim.lsp.tagfunc directly with lazy loading and document its
  implementation instead of maintaining a wrapper.
- Group files in section_order, replacing append_only and merge_files.
  Merge declarations before sorting each section, placing shared helpers
  in the main vim section and keeping vim.inspect in alphabetical order.
- Add basic parser coverage for callable modules and module links.

AI-assisted
This commit is contained in:
Lewis Russell authored and Lewis Russell committed 2026-09-30 17:56:01 +01:00
1 parent 9f7fb8b27d
commit 364b7a3402
28 files changed
+919 -711

No files matched your search

+1 -2
View File
@@ -27,8 +27,6 @@
},
"diagnostics": {
"disable": [
"annotation-usage-error",
"code-style-check",
"incomplete-signature-doc",
"iter-variable-reassign",
"unnecessary-if"
@@ -42,6 +40,7 @@
"unused": "warning"
},
"enables": [
"code-style-check",
"missing-global-doc",
"non-literal-expressions-in-assert",
"unknown-doc-tag"
+1
View File
@@ -248,6 +248,7 @@ Docstring format:
0), it is marked as "experimental".
- See scripts/util.lua for the mapping of api-level to Nvim version.
- Use `@deprecated` to mark deprecated functions.
- For callable module tables, document `__call` in `setmetatable(M, { ... })`.
- Use `@nodoc` to prevent documentation generation.
- Use `@private` for private functions.
- Naming a function with an underscore prefix ("_foo", not "foo") implies
+14
View File
@@ -380,6 +380,20 @@ run({func}, {...}) *vim.async.run()*
Return: ~
(`vim.async.Task<R...>`)
semaphore({permits}) *vim.async.semaphore()*
Create an async semaphore that allows up to a given number of
acquisitions.
Prefer |Semaphore:with()| for most uses so permits are released reliably.
Use |Semaphore:acquire()| and |Semaphore:release()| directly only when the
acquire and release points cannot be expressed as one function call.
Parameters: ~
• {permits} (`integer?`) (default: 1)
Return: ~
(`vim.async.Semaphore`)
Semaphore:acquire() *Semaphore:acquire()*
Acquire a semaphore permit.
+456 -446
View File
@@ -1182,6 +1182,36 @@ vim.wo[{winid}][{bufnr}] *vim.wo*
==============================================================================
Lua module: vim *lua-vim*
*vim.Ringbuf*
Fields: ~
• {clear} (`fun()`) See |Ringbuf:clear()|.
• {peek} (`fun(): T?`) See |Ringbuf:peek()|.
• {pop} (`fun(): T?`) See |Ringbuf:pop()|.
• {push} (`fun(item: T)`) See |Ringbuf:push()|.
Ringbuf:clear() *Ringbuf:clear()*
Clear all items
Ringbuf:peek() *Ringbuf:peek()*
Returns the first unread item without removing it
Return: ~
(`T?`)
Ringbuf:pop() *Ringbuf:pop()*
Removes and returns the first unread item
Return: ~
(`T?`)
Ringbuf:push({item}) *Ringbuf:push()*
Adds an item, overriding the oldest item if the buffer is full.
Parameters: ~
• {item} (`T`)
vim.cmd({cmd}) *vim.cmd()*
Executes Vimscript (|Ex-command|s).
@@ -1228,443 +1258,6 @@ vim.cmd({cmd}) *vim.cmd()*
See also: ~
• |ex-cmd-index|
vim.defer_fn({fn}, {timeout}) *vim.defer_fn()*
Defers calling {fn} until {timeout} ms passes.
Use to do a one-shot timer that calls {fn}. Note: The {fn} is |schedule|d
automatically, so API functions are safe to call.
Parameters: ~
• {fn} (`function`) Callback to call once `timeout` expires
• {timeout} (`integer`) Number of milliseconds to wait before calling
`fn`
Return: ~
(`uv.uv_timer_t`) timer luv timer object
*vim.deprecate()*
vim.deprecate({name}, {alternative}, {version}, {plugin}, {backtrace})
Shows a deprecation message to the user.
Parameters: ~
• {name} (`string`) Deprecated feature (function, API, etc.).
• {alternative} (`string?`) Suggested alternative feature.
• {version} (`string`) Version when the deprecated function will be
removed.
• {plugin} (`string?`) Name of the plugin that owns the deprecated
feature. Defaults to "Nvim".
• {backtrace} (`boolean?`) Prints backtrace. Defaults to true.
Return: ~
(`string?`) Deprecation message, or nil if no message was shown.
vim.inspect() *vim.inspect()*
Gets a human-readable representation of the given object.
Overloads: ~
• `fun(x: any, opts?: vim.inspect.Opts): string`
Return: ~
(`string`)
See also: ~
• |vim.print()|
• https://github.com/kikito/inspect.lua
• https://github.com/mpeterv/vinspect
vim.keycode({keys}, {info}) *vim.keycode()*
Converts keys from |key-notation| to the internal encoding. Optionally
returns structured *key-chord* info as retval 2.
Inverse of |keytrans()|, which converts the internal encoding back to
|key-notation|.
Examples: >lua
local k = vim.keycode
vim.g.mapleader = k'<bs>'
-- Split a key sequence into chords, e.g. to inspect modifiers.
local _, chords = vim.keycode('<C-w>v', true)
assert(chords[1].key == 'w' and chords[1].mod[1] == 'C')
-- keytrans() is the inverse: internal encoding => key-notation.
assert(vim.fn.keytrans(vim.keycode('<C-a>')) == '<C-A>')
<
Parameters: ~
• {keys} (`string`) Keys in |key-notation|.
• {info} (`boolean?`) Also return key-chord info.
Return (multiple): ~
(`string`) Internal representation of the given `keys`.
(`table?`) List of parsed key-chords, each with fields:
• {key} (`string`) Key without modifiers. Example: `<C-A>` has
`key="a"`.
• {key_alt} (`string?`) Alternative spelling of `key`, or nil if There
Is No Alternative (TINA). Example: `"<"` has `key_alt="lt"`.
• {keys} (`string`) Full key-chord in canonical key-notation (as
produced by |keytrans()|), including modifiers. Example: `<A-f>` has
`keys="<M-f>"`.
• {mod} (`('M'|'T'|'C'|'S'|'2'|'3'|'4'|'D')[]`) List of key-chord
modifiers. Example: `<C-S-f>` has `mod={ 'C', 'S' }`.
See also: ~
• |nvim_replace_termcodes()|
• |keytrans()|
vim.lua_omnifunc({find_start}) *vim.lua_omnifunc()*
Omnifunc for completing Lua values from the runtime Lua interpreter,
similar to the builtin completion for the `:lua` command.
Activate using `vim.bo.omnifunc = vim.lua_omnifunc` in a Lua buffer.
Parameters: ~
• {find_start} (`1|0`)
vim.notify({msg}, {level}, {opts}) *vim.notify()*
Displays a notification to the user.
This function can be overridden by plugins to display notifications using
a custom provider (such as the system notification provider). By default,
writes to |:messages|.
Parameters: ~
• {msg} (`string`) Content of the notification to show to the user.
• {level} (`integer?`) One of the values from |vim.log.levels|.
• {opts} (`table?`) Optional parameters. Unused by default.
vim.notify_once({msg}, {level}, {opts}) *vim.notify_once()*
Displays a notification only one time.
Like |vim.notify()|, but subsequent calls with the same message will not
display a notification.
Parameters: ~
• {msg} (`string`) Content of the notification to show to the user.
• {level} (`integer?`) One of the values from |vim.log.levels|.
• {opts} (`table?`) Optional parameters. Unused by default.
Return: ~
(`boolean`) true if message was displayed, else false
vim.on_key({fn}, {ns_id}, {opts}) *vim.on_key()*
Registers function {fn} with |namespace| {ns_id} as a listener to EVERY
input key (except keys consumed by |getchar()|, by {fn} itself, or by a
<Cmd> payload).
To parse |key-chord|s, see |vim.keycode()|. Example: >lua
local keychords = vim.keycode(vim.fn.keytrans(key), true)
<
Note: ~
• If {fn} returns an empty string, {key} is discarded/ignored; if {key}
is <Cmd> then the "<Cmd>…<CR>" sequence is discarded as a whole.
• Non-recursive: if {fn} itself consumes input, it won't be invoked for
those keys.
• Not invoked for keys consumed by getchar()/getcharstr(): they never
reach the main loop.
• To UNregister a given {ns_id}, pass `nil` {fn}.
• {fn} will be removed on error.
• {fn} will not be cleared by |nvim_buf_clear_namespace()|
• The |-w| command-line option is related but does not support callbacks
and cannot be toggled dynamically.
Parameters: ~
• {fn} (`fun(key: string, typed: string): string??`) Function
invoked for every input key, after mappings have been applied
but before further processing. Arguments {key} and {typed}
are raw input (use |keytrans()| to get |keycodes|).
• {key} is the key after mappings are applied.
• {typed} is the input before mappings are applied; may be
empty if {key} was produced by non-typed key(s) or by the
same typed key(s) that produced a previous {key}.
• {ns_id} (`integer?`) Namespace ID. If nil or 0, returns a new
|namespace| id.
• {opts} (`table?`) Optional parameters
Return: ~
(`integer`) Namespace id associated with {fn}. Or count of all
callbacks if on_key() is called without arguments.
See also: ~
• |keytrans()|
• |vim.keycode()|
vim.paste({lines}, {phase}) *vim.paste()*
Paste handler, invoked by |nvim_paste()|.
Note: This is provided only as a "hook", don't call it directly; call
|nvim_paste()| instead, which arranges redo (dot-repeat) and invokes
`vim.paste`.
Example: To remove ANSI color codes when pasting: >lua
vim.paste = (function(overridden)
return function(lines, phase)
for i,line in ipairs(lines) do
-- Scrub ANSI color codes from paste input.
lines[i] = line:gsub('\27%[[0-9;mK]+', '')
end
return overridden(lines, phase)
end
end)(vim.paste)
<
Parameters: ~
• {lines} (`string[]`) |readfile()|-style list of lines to paste.
|channel-lines|
• {phase} (`-1|1|2|3`) -1: "non-streaming" paste: the call contains all
lines. If paste is "streamed", `phase` indicates the stream
state:
• 1: starts the paste (exactly once)
• 2: continues the paste (zero or more times)
• 3: ends the paste (exactly once)
Return: ~
(`boolean`) result false if client should cancel the paste.
See also: ~
• |paste|
vim.print({...}) *vim.print()*
"Pretty prints" the given arguments and returns them unmodified.
Example: >lua
local hl_normal = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' }))
<
Parameters: ~
• {...} (`any`)
Return: ~
(`any`) given arguments.
See also: ~
• |vim.inspect()|
• |:=|
vim.schedule_wrap({fn}) *vim.schedule_wrap()*
Returns a function which calls {fn} via |vim.schedule()|.
The returned function passes all arguments to {fn}.
Example: >lua
function notify_readable(_err, readable)
vim.notify("readable? " .. tostring(readable))
end
vim.uv.fs_access(vim.fn.stdpath("config"), "R", vim.schedule_wrap(notify_readable))
<
Parameters: ~
• {fn} (`function`)
Return: ~
(`function`)
See also: ~
• |lua-loop-callbacks|
• |vim.schedule()|
• |vim.in_fast_event()|
*vim.str_byteindex()*
vim.str_byteindex({s}, {encoding}, {index}, {strict_indexing})
Convert UTF-32, UTF-16 or UTF-8 {index} to byte index. If
{strict_indexing} is false then an out of range index will return byte
length instead of throwing an error.
Invalid UTF-8 and NUL is treated like in |vim.str_utfindex()|. An {index}
in the middle of a UTF-16 sequence is rounded upwards to the end of that
sequence.
Parameters: ~
• {s} (`string`)
• {encoding} (`"utf-8"|"utf-16"|"utf-32"`)
• {index} (`integer`)
• {strict_indexing} (`boolean?`) default: true
Return: ~
(`integer`)
*vim.str_utfindex()*
vim.str_utfindex({s}, {encoding}, {index}, {strict_indexing})
Convert byte index to UTF-32, UTF-16 or UTF-8 indices. If {index} is not
supplied, the length of the string is used. All indices are zero-based.
If {strict_indexing} is false then an out of range index will return
string length instead of throwing an error. Invalid UTF-8 bytes, and
embedded surrogates are counted as one code point each. An {index} in the
middle of a UTF-8 sequence is rounded upwards to the end of that sequence.
Parameters: ~
• {s} (`string`)
• {encoding} (`"utf-8"|"utf-16"|"utf-32"`)
• {index} (`integer?`)
• {strict_indexing} (`boolean?`) default: true
Return: ~
(`integer`)
vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
Waits up to `time` milliseconds, until `callback` returns `true`
(success). Executes `callback` immediately, then on user events, internal
events, and approximately every `interval` milliseconds (default 200).
Returns `true` plus any remaining callback results on success.
Nvim processes other events while waiting. Cannot be called during an
|api-fast| event.
Examples: >lua
-- Wait for 100 ms, allowing other events to process.
vim.wait(100)
-- Wait up to 1000 ms or until `vim.g.foo` is true, at intervals of ~500 ms.
vim.wait(1000, function() return vim.g.foo end, 500)
-- Wait indefinitely until `vim.g.foo` is true, and get the callback results.
local ok, rv1, rv2, rv3 = vim.wait(math.huge, function()
return vim.g.foo, 'a', 42, { ok = { 'yes' } }
end)
-- Schedule a function to set a value in 100ms. This would wait 10s if blocked, but actually
-- only waits 100ms because `vim.wait` processes other events while waiting.
vim.defer_fn(function() vim.g.timer_result = true end, 100)
if vim.wait(10000, function() return vim.g.timer_result end) then
print('Only waiting a little bit of time!')
end
-- Yield via vim.wait() to allow CTRL-C to interrupt Lua code.
while true do
-- ...work...
local _, code = vim.wait(0, nil, 0)
if code == -2 then -- CTRL-C.
break
end
end
<
Parameters: ~
• {time} (`number`) Number of milliseconds to wait. Must be
non-negative number, any fractional part is truncated.
• {callback} (`fun(): boolean, ...?`) Optional callback. Waits until
{callback} returns true
• {interval} (`integer?`) (Approximate) number of milliseconds to wait
between polls
• {fast_only} (`boolean?`) If true, only |api-fast| events will be
processed.
Return (multiple): ~
(`boolean`)
(`-1|-2?`)
• If callback returns `true` before timeout: `true, ...` (remaining
callback results).
• On timeout: `false, -1`
• On interrupt: `false, -2`
• On error: the error is raised.
==============================================================================
Lua module: vim.inspector *vim.inspector*
vim.inspect_pos({buf}, {row}, {col}, {filter}) *vim.inspect_pos()*
Get all the items at a given buffer position.
Can also be pretty-printed with `:Inspect!`. *:Inspect!*
Attributes: ~
Since: 0.9.0
Parameters: ~
• {buf} (`integer?`) defaults to the current buffer
• {row} (`integer?`) row to inspect, 0-based. Defaults to the row of
the current cursor
• {col} (`integer?`) col to inspect, 0-based. Defaults to the col of
the current cursor
• {filter} (`table?`) Table with key-value pairs to filter the items
• {extmarks} (`boolean|"all"`, default: true) Include
extmarks. When `all`, then extmarks without a `hl_group`
will also be included.
• {semantic_tokens} (`boolean`, default: true) Include
semantic token highlights.
• {syntax} (`boolean`, default: `true`) Include syntax based
highlight groups.
• {treesitter} (`boolean`, default: `true`) Include
treesitter based highlight groups.
Return: ~
(`table`) a table with the following key-value pairs. Items are in
"traversal order":
• treesitter: a list of treesitter captures
• syntax: a list of syntax groups
• semantic_tokens: a list of semantic tokens
• extmarks: a list of extmarks
• buffer: the buffer used to get the items
• row: the row used to get the items
• col: the col used to get the items
vim.show_pos({buf}, {row}, {col}, {filter}) *vim.show_pos()*
Show all the items at a given buffer position.
Can also be shown with `:Inspect`. *:Inspect*
See also |:marks| to list all extmarks.
Example: To bind this function to the vim-scriptease inspired `zS` in
Normal mode: >lua
vim.keymap.set('n', 'zS', vim.show_pos)
<
Attributes: ~
Since: 0.9.0
Parameters: ~
• {buf} (`integer?`) defaults to the current buffer
• {row} (`integer?`) row to inspect, 0-based. Defaults to the row of
the current cursor
• {col} (`integer?`) col to inspect, 0-based. Defaults to the col of
the current cursor
• {filter} (`table?`) A table with the following fields:
• {extmarks} (`boolean|"all"`, default: true) Include
extmarks. When `all`, then extmarks without a `hl_group`
will also be included.
• {semantic_tokens} (`boolean`, default: true) Include
semantic token highlights.
• {syntax} (`boolean`, default: `true`) Include syntax based
highlight groups.
• {treesitter} (`boolean`, default: `true`) Include
treesitter based highlight groups.
*vim.Ringbuf*
Fields: ~
• {clear} (`fun()`) See |Ringbuf:clear()|.
• {peek} (`fun(): T?`) See |Ringbuf:peek()|.
• {pop} (`fun(): T?`) See |Ringbuf:pop()|.
• {push} (`fun(item: T)`) See |Ringbuf:push()|.
Ringbuf:clear() *Ringbuf:clear()*
Clear all items
Ringbuf:peek() *Ringbuf:peek()*
Returns the first unread item without removing it
Return: ~
(`T?`)
Ringbuf:pop() *Ringbuf:pop()*
Removes and returns the first unread item
Return: ~
(`T?`)
Ringbuf:push({item}) *Ringbuf:push()*
Adds an item, overriding the oldest item if the buffer is full.
Parameters: ~
• {item} (`T`)
vim.deep_equal({a}, {b}) *vim.deep_equal()*
Deep compare values for equality
@@ -1718,6 +1311,36 @@ vim.defaulttable({createfn}) *vim.defaulttable()*
Return: ~
(`table`) Empty table with `__index` metamethod.
vim.defer_fn({fn}, {timeout}) *vim.defer_fn()*
Defers calling {fn} until {timeout} ms passes.
Use to do a one-shot timer that calls {fn}. Note: The {fn} is |schedule|d
automatically, so API functions are safe to call.
Parameters: ~
• {fn} (`function`) Callback to call once `timeout` expires
• {timeout} (`integer`) Number of milliseconds to wait before calling
`fn`
Return: ~
(`uv.uv_timer_t`) timer luv timer object
*vim.deprecate()*
vim.deprecate({name}, {alternative}, {version}, {plugin}, {backtrace})
Shows a deprecation message to the user.
Parameters: ~
• {name} (`string`) Deprecated feature (function, API, etc.).
• {alternative} (`string?`) Suggested alternative feature.
• {version} (`string`) Version when the deprecated function will be
removed.
• {plugin} (`string?`) Name of the plugin that owns the deprecated
feature. Defaults to "Nvim".
• {backtrace} (`boolean?`) Prints backtrace. Defaults to true.
Return: ~
(`string?`) Deprecation message, or nil if no message was shown.
vim.endswith({s}, {suffix}) *vim.endswith()*
Tests if `s` ends with `suffix`.
@@ -1764,6 +1387,34 @@ vim.gsplit({s}, {sep}, {opts}) *vim.gsplit()*
• https://www.lua.org/pil/20.2.html
• http://lua-users.org/wiki/StringLibraryTutorial
vim.inspect({root}, {options}) *vim.inspect()*
Gets a human-readable representation of the given object.
Parameters: ~
• {root} (`any`)
• {options} (`table?`) A table with the following fields:
• {depth}? (`integer`, default: unlimited) Maximum table
nesting depth. Deeper tables are shown as `{...}`.
• {indent}? (`string`, default: `" "`) String repeated for
each level of indentation.
• {newline}? (`string`, default: `"\n"`) String used for
line breaks.
• {process}? (`fun(item: any, path: any[]): any`)
Transforms values, keys, and metatables before
formatting. Return a replacement value, or `nil` to omit
a table entry. {path} lists the keys leading to {item},
with `vim.inspect.KEY` appended for keys or
`vim.inspect.METATABLE` for metatables. The root has an
empty path.
Return: ~
(`string`)
See also: ~
• |vim.print()|
• https://github.com/kikito/inspect.lua
• https://github.com/mpeterv/vinspect
vim.is_callable({f}) *vim.is_callable()*
Returns true if object `f` can be called as a function.
@@ -1822,6 +1473,46 @@ vim.isnil({t}) *vim.isnil()*
Return: ~
(`boolean`) `true` if `nil` or |vim.NIL|, else `false`.
vim.keycode({keys}, {info}) *vim.keycode()*
Converts keys from |key-notation| to the internal encoding. Optionally
returns structured *key-chord* info as retval 2.
Inverse of |keytrans()|, which converts the internal encoding back to
|key-notation|.
Examples: >lua
local k = vim.keycode
vim.g.mapleader = k'<bs>'
-- Split a key sequence into chords, e.g. to inspect modifiers.
local _, chords = vim.keycode('<C-w>v', true)
assert(chords[1].key == 'w' and chords[1].mod[1] == 'C')
-- keytrans() is the inverse: internal encoding => key-notation.
assert(vim.fn.keytrans(vim.keycode('<C-a>')) == '<C-A>')
<
Parameters: ~
• {keys} (`string`) Keys in |key-notation|.
• {info} (`boolean?`) Also return key-chord info.
Return (multiple): ~
(`string`) Internal representation of the given `keys`.
(`table?`) List of parsed key-chords, each with fields:
• {key} (`string`) Key without modifiers. Example: `<C-A>` has
`key="a"`.
• {key_alt} (`string?`) Alternative spelling of `key`, or nil if There
Is No Alternative (TINA). Example: `"<"` has `key_alt="lt"`.
• {keys} (`string`) Full key-chord in canonical key-notation (as
produced by |keytrans()|), including modifiers. Example: `<A-f>` has
`keys="<M-f>"`.
• {mod} (`('M'|'T'|'C'|'S'|'2'|'3'|'4'|'D')[]`) List of key-chord
modifiers. Example: `<C-S-f>` has `mod={ 'C', 'S' }`.
See also: ~
• |nvim_replace_termcodes()|
• |keytrans()|
vim.list.bisect({t}, {val}, {opts}) *vim.list.bisect()*
Search for a position in a sorted |lua-list| {t} where {val} can be
inserted while keeping the list sorted.
@@ -1970,6 +1661,15 @@ vim.list_slice({list}, {start}, {finish}) *vim.list_slice()*
Return: ~
(`any[]`) Copy of table sliced from start to finish (inclusive)
vim.lua_omnifunc({find_start}) *vim.lua_omnifunc()*
Omnifunc for completing Lua values from the runtime Lua interpreter,
similar to the builtin completion for the `:lua` command.
Activate using `vim.bo.omnifunc = vim.lua_omnifunc` in a Lua buffer.
Parameters: ~
• {find_start} (`1|0`)
vim.nonnil({...}) *vim.nonnil()*
Returns the first argument which is not nil.
@@ -1992,6 +1692,32 @@ vim.nonnil({...}) *vim.nonnil()*
Return: ~
(`any`)
vim.notify({msg}, {level}, {opts}) *vim.notify()*
Displays a notification to the user.
This function can be overridden by plugins to display notifications using
a custom provider (such as the system notification provider). By default,
writes to |:messages|.
Parameters: ~
• {msg} (`string`) Content of the notification to show to the user.
• {level} (`integer?`) One of the values from |vim.log.levels|.
• {opts} (`table?`) Optional parameters. Unused by default.
vim.notify_once({msg}, {level}, {opts}) *vim.notify_once()*
Displays a notification only one time.
Like |vim.notify()|, but subsequent calls with the same message will not
display a notification.
Parameters: ~
• {msg} (`string`) Content of the notification to show to the user.
• {level} (`integer?`) One of the values from |vim.log.levels|.
• {opts} (`table?`) Optional parameters. Unused by default.
Return: ~
(`boolean`) true if message was displayed, else false
vim.npcall({fn}, {...}) *vim.npcall()*
Calls the function `fn` in `protected mode` like |pcall()|, but returns
`nil` on error.
@@ -2006,6 +1732,84 @@ vim.npcall({fn}, {...}) *vim.npcall()*
Return: ~
(`any`) ...
vim.on_key({fn}, {ns_id}, {opts}) *vim.on_key()*
Registers function {fn} with |namespace| {ns_id} as a listener to EVERY
input key (except keys consumed by |getchar()|, by {fn} itself, or by a
<Cmd> payload).
To parse |key-chord|s, see |vim.keycode()|. Example: >lua
local keychords = vim.keycode(vim.fn.keytrans(key), true)
<
Note: ~
• If {fn} returns an empty string, {key} is discarded/ignored; if {key}
is <Cmd> then the "<Cmd>…<CR>" sequence is discarded as a whole.
• Non-recursive: if {fn} itself consumes input, it won't be invoked for
those keys.
• Not invoked for keys consumed by getchar()/getcharstr(): they never
reach the main loop.
• To UNregister a given {ns_id}, pass `nil` {fn}.
• {fn} will be removed on error.
• {fn} will not be cleared by |nvim_buf_clear_namespace()|
• The |-w| command-line option is related but does not support callbacks
and cannot be toggled dynamically.
Parameters: ~
• {fn} (`fun(key: string, typed: string): string??`) Function
invoked for every input key, after mappings have been applied
but before further processing. Arguments {key} and {typed}
are raw input (use |keytrans()| to get |keycodes|).
• {key} is the key after mappings are applied.
• {typed} is the input before mappings are applied; may be
empty if {key} was produced by non-typed key(s) or by the
same typed key(s) that produced a previous {key}.
• {ns_id} (`integer?`) Namespace ID. If nil or 0, returns a new
|namespace| id.
• {opts} (`table?`) Optional parameters
Return: ~
(`integer`) Namespace id associated with {fn}. Or count of all
callbacks if on_key() is called without arguments.
See also: ~
• |keytrans()|
• |vim.keycode()|
vim.paste({lines}, {phase}) *vim.paste()*
Paste handler, invoked by |nvim_paste()|.
Note: This is provided only as a "hook", don't call it directly; call
|nvim_paste()| instead, which arranges redo (dot-repeat) and invokes
`vim.paste`.
Example: To remove ANSI color codes when pasting: >lua
vim.paste = (function(overridden)
return function(lines, phase)
for i,line in ipairs(lines) do
-- Scrub ANSI color codes from paste input.
lines[i] = line:gsub('\27%[[0-9;mK]+', '')
end
return overridden(lines, phase)
end
end)(vim.paste)
<
Parameters: ~
• {lines} (`string[]`) |readfile()|-style list of lines to paste.
|channel-lines|
• {phase} (`-1|1|2|3`) -1: "non-streaming" paste: the call contains all
lines. If paste is "streamed", `phase` indicates the stream
state:
• 1: starts the paste (exactly once)
• 2: continues the paste (zero or more times)
• 3: ends the paste (exactly once)
Return: ~
(`boolean`) result false if client should cancel the paste.
See also: ~
• |paste|
vim.pesc({s}) *vim.pesc()*
Escapes magic chars in |lua-pattern|s.
@@ -2018,6 +1822,23 @@ vim.pesc({s}) *vim.pesc()*
See also: ~
• https://github.com/rxi/lume
vim.print({...}) *vim.print()*
"Pretty prints" the given arguments and returns them unmodified.
Example: >lua
local hl_normal = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' }))
<
Parameters: ~
• {...} (`any`)
Return: ~
(`any`) given arguments.
See also: ~
• |vim.inspect()|
• |:=|
vim.ringbuf({size}) *vim.ringbuf()*
Create a ring buffer limited to a maximal number of items. Once the buffer
is full, adding a new entry overrides the oldest entry. >lua
@@ -2048,6 +1869,29 @@ vim.ringbuf({size}) *vim.ringbuf()*
Return: ~
(`vim.Ringbuf<any>`) ringbuf
vim.schedule_wrap({fn}) *vim.schedule_wrap()*
Returns a function which calls {fn} via |vim.schedule()|.
The returned function passes all arguments to {fn}.
Example: >lua
function notify_readable(_err, readable)
vim.notify("readable? " .. tostring(readable))
end
vim.uv.fs_access(vim.fn.stdpath("config"), "R", vim.schedule_wrap(notify_readable))
<
Parameters: ~
• {fn} (`function`)
Return: ~
(`function`)
See also: ~
• |lua-loop-callbacks|
• |vim.schedule()|
• |vim.in_fast_event()|
vim.spairs({t}) *vim.spairs()*
Enumerates key-value pairs of a table, ordered by key.
@@ -2100,6 +1944,44 @@ vim.startswith({s}, {prefix}) *vim.startswith()*
Return: ~
(`boolean`) `true` if `prefix` is a prefix of `s`
*vim.str_byteindex()*
vim.str_byteindex({s}, {encoding}, {index}, {strict_indexing})
Convert UTF-32, UTF-16 or UTF-8 {index} to byte index. If
{strict_indexing} is false then an out of range index will return byte
length instead of throwing an error.
Invalid UTF-8 and NUL is treated like in |vim.str_utfindex()|. An {index}
in the middle of a UTF-16 sequence is rounded upwards to the end of that
sequence.
Parameters: ~
• {s} (`string`)
• {encoding} (`"utf-8"|"utf-16"|"utf-32"`)
• {index} (`integer`)
• {strict_indexing} (`boolean?`) default: true
Return: ~
(`integer`)
*vim.str_utfindex()*
vim.str_utfindex({s}, {encoding}, {index}, {strict_indexing})
Convert byte index to UTF-32, UTF-16 or UTF-8 indices. If {index} is not
supplied, the length of the string is used. All indices are zero-based.
If {strict_indexing} is false then an out of range index will return
string length instead of throwing an error. Invalid UTF-8 bytes, and
embedded surrogates are counted as one code point each. An {index} in the
middle of a UTF-8 sequence is rounded upwards to the end of that sequence.
Parameters: ~
• {s} (`string`)
• {encoding} (`"utf-8"|"utf-16"|"utf-32"`)
• {index} (`integer?`)
• {strict_indexing} (`boolean?`) default: true
Return: ~
(`integer`)
vim.tbl_contains({t}, {value}, {opts}) *vim.tbl_contains()*
Checks if a table contains a given value, specified either directly or via
a predicate that is checked for each value.
@@ -2358,6 +2240,135 @@ vim.validate({name}, {value}, {validator}, {optional}, {message})
• `fun(name: string, val: any, validator: vim.validate.Validator, message: string)`
• `fun(spec: table<string,[any, vim.validate.Validator, boolean|string]>)`
vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
Waits up to `time` milliseconds, until `callback` returns `true`
(success). Executes `callback` immediately, then on user events, internal
events, and approximately every `interval` milliseconds (default 200).
Returns `true` plus any remaining callback results on success.
Nvim processes other events while waiting. Cannot be called during an
|api-fast| event.
Examples: >lua
-- Wait for 100 ms, allowing other events to process.
vim.wait(100)
-- Wait up to 1000 ms or until `vim.g.foo` is true, at intervals of ~500 ms.
vim.wait(1000, function() return vim.g.foo end, 500)
-- Wait indefinitely until `vim.g.foo` is true, and get the callback results.
local ok, rv1, rv2, rv3 = vim.wait(math.huge, function()
return vim.g.foo, 'a', 42, { ok = { 'yes' } }
end)
-- Schedule a function to set a value in 100ms. This would wait 10s if blocked, but actually
-- only waits 100ms because `vim.wait` processes other events while waiting.
vim.defer_fn(function() vim.g.timer_result = true end, 100)
if vim.wait(10000, function() return vim.g.timer_result end) then
print('Only waiting a little bit of time!')
end
-- Yield via vim.wait() to allow CTRL-C to interrupt Lua code.
while true do
-- ...work...
local _, code = vim.wait(0, nil, 0)
if code == -2 then -- CTRL-C.
break
end
end
<
Parameters: ~
• {time} (`number`) Number of milliseconds to wait. Must be
non-negative number, any fractional part is truncated.
• {callback} (`fun(): boolean, ...?`) Optional callback. Waits until
{callback} returns true
• {interval} (`integer?`) (Approximate) number of milliseconds to wait
between polls
• {fast_only} (`boolean?`) If true, only |api-fast| events will be
processed.
Return (multiple): ~
(`boolean`)
(`-1|-2?`)
• If callback returns `true` before timeout: `true, ...` (remaining
callback results).
• On timeout: `false, -1`
• On interrupt: `false, -2`
• On error: the error is raised.
==============================================================================
Lua module: vim.inspector *vim.inspector*
vim.inspect_pos({buf}, {row}, {col}, {filter}) *vim.inspect_pos()*
Get all the items at a given buffer position.
Can also be pretty-printed with `:Inspect!`. *:Inspect!*
Attributes: ~
Since: 0.9.0
Parameters: ~
• {buf} (`integer?`) defaults to the current buffer
• {row} (`integer?`) row to inspect, 0-based. Defaults to the row of
the current cursor
• {col} (`integer?`) col to inspect, 0-based. Defaults to the col of
the current cursor
• {filter} (`table?`) Table with key-value pairs to filter the items
• {extmarks} (`boolean|"all"`, default: true) Include
extmarks. When `all`, then extmarks without a `hl_group`
will also be included.
• {semantic_tokens} (`boolean`, default: true) Include
semantic token highlights.
• {syntax} (`boolean`, default: `true`) Include syntax based
highlight groups.
• {treesitter} (`boolean`, default: `true`) Include
treesitter based highlight groups.
Return: ~
(`table`) a table with the following key-value pairs. Items are in
"traversal order":
• treesitter: a list of treesitter captures
• syntax: a list of syntax groups
• semantic_tokens: a list of semantic tokens
• extmarks: a list of extmarks
• buffer: the buffer used to get the items
• row: the row used to get the items
• col: the col used to get the items
vim.show_pos({buf}, {row}, {col}, {filter}) *vim.show_pos()*
Show all the items at a given buffer position.
Can also be shown with `:Inspect`. *:Inspect*
See also |:marks| to list all extmarks.
Example: To bind this function to the vim-scriptease inspired `zS` in
Normal mode: >lua
vim.keymap.set('n', 'zS', vim.show_pos)
<
Attributes: ~
Since: 0.9.0
Parameters: ~
• {buf} (`integer?`) defaults to the current buffer
• {row} (`integer?`) row to inspect, 0-based. Defaults to the row of
the current cursor
• {col} (`integer?`) col to inspect, 0-based. Defaults to the col of
the current cursor
• {filter} (`table?`) A table with the following fields:
• {extmarks} (`boolean|"all"`, default: true) Include
extmarks. When `all`, then extmarks without a `hl_group`
will also be included.
• {semantic_tokens} (`boolean`, default: true) Include
semantic token highlights.
• {syntax} (`boolean`, default: `true`) Include syntax based
highlight groups.
• {treesitter} (`boolean`, default: `true`) Include
treesitter based highlight groups.
==============================================================================
Lua module: vim.base64 *vim.base64*
@@ -3306,10 +3317,7 @@ Iter:find({f}) *Iter:find()*
Since: 0.10.0
Parameters: ~
• {f} (`(fun(v: V1, ...: V...): boolean)|any`)
Overloads: ~
• `fun<V1, V...>(self: vim.IterArray<V1, V...>, f: V1|fun(v: V1, ...: V...): boolean): V1?, V...`
• {f} (`V1|fun(v: V1, ...: V...): any`)
Return (multiple): ~
(`V1?`)
@@ -3584,10 +3592,6 @@ Iter:totable() *Iter:totable()*
Attributes: ~
Since: 0.10.0
Overloads: ~
• `fun<T>(self: vim.Iter<T, never>): T[]`
• `fun<V1, V2, V...>(self: vim.Iter<V1, V2, V...>): [V1, V2, V...][]`
Return: ~
(`any[]`)
@@ -5871,7 +5875,7 @@ Example: >lua
end
<
*vim.version()* returns the version of the current Nvim process.
|vim.version()| returns the version of the current Nvim process.
VERSION RANGE SPEC *version-range*
@@ -5948,6 +5952,12 @@ VersionRange:has({version}) *VersionRange:has()*
See also: ~
• https://github.com/npm/node-semver#ranges
vim.version() *vim.version()*
Returns the current Nvim version.
Return: ~
(`vim.Version`)
vim.version.cmp({v1}, {v2}) *vim.version.cmp()*
Parses and compares two version objects (the result of
|vim.version.parse()|, or specified literally as a `{major, minor, patch}`
+15 -19
View File
@@ -1577,6 +1577,21 @@ This Lua |treesitter-query| interface allows you to create queries and use
them to parse text. See |vim.treesitter.query.parse()| for a working example.
*TSQuery*
Extends: |userdata|
Reference to an object held by the treesitter library that is used as a
component of the |vim.treesitter.Query| for language feature support. See
|treesitter-query| for more about queries or
|vim.treesitter.query.parse()| for an example of how to obtain a query
object.
Fields: ~
• {disable_capture} (`fun(self: TSQuery, capture_name: string)`) See
|TSQuery:disable_capture()|.
• {disable_pattern} (`fun(self: TSQuery, pattern_index: integer)`) See
|TSQuery:disable_pattern()|.
*vim.treesitter.Query*
Parsed query, see |vim.treesitter.query.parse()|
@@ -1871,25 +1886,6 @@ set({lang}, {query_name}, {text}) *vim.treesitter.query.set()*
• {query_name} (`string`) Name of the query (e.g., "highlights")
• {text} (`string`) Query text (unparsed).
*TSQuery*
Extends: |userdata|
Reference to an object held by the treesitter library that is used as a
component of the |vim.treesitter.Query| for language feature support. See
|treesitter-query| for more about queries or
|vim.treesitter.query.parse()| for an example of how to obtain a query
object.
Fields: ~
• {disable_capture} (`fun(self: TSQuery, capture_name: string)`) See
|TSQuery:disable_capture()|.
• {disable_pattern} (`fun(self: TSQuery, pattern_index: integer)`) See
|TSQuery:disable_pattern()|.
TSQuery:disable_capture({capture_name}) *TSQuery:disable_capture()*
Disable a specific capture in this query; once disabled the capture cannot
be re-enabled. {capture_name} should not include a leading "@".
+1 -1
View File
@@ -55,7 +55,7 @@ local function check_line(line)
{ details = true }
))
:filter(function(extmark)
return not extmark[4].invalid
return not assert(extmark[4]).invalid
end)
:totable()
+1 -17
View File
@@ -243,22 +243,6 @@ function vim._os_proc_children(ppid)
return children
end
--- @nodoc
--- @class vim.inspect.Opts
--- @field depth? integer
--- @field newline? string
--- @field indent? string
--- @field process? fun(item: any, path: any[]): any
--- Gets a human-readable representation of the given object.
---
---@see |vim.print()|
---@see https://github.com/kikito/inspect.lua
---@see https://github.com/mpeterv/vinspect
---@return string
---@overload fun(x: any, opts?: vim.inspect.Opts): string
vim.inspect = vim.inspect
do
local startpos --- @type integer[]?
local tdots = 0
@@ -1119,7 +1103,7 @@ function vim._expand_pat(pat, env)
local vars = vim.fn.getcompletion(prefix .. match_part, type) --- @type string[]
insert_keys(vim
.iter(vars)
:map(function(s) ---@param s string
:map(function(s)
s = s:gsub('[()]+$', '') -- strip '(' and ')' for function completions
return s:sub(#prefix + 1) -- strip the prefix, e.g., 'g:foo' => 'foo'
end)
-1
View File
@@ -189,7 +189,6 @@ function M.lsp_complete(_, line)
local subcmd = split[2]
return vim
.iter(complete_args[subcmd]())
--- @param n string
:map(function(n)
return vim.fn.escape(n, ' \t')
end)
+1 -1
View File
@@ -37,7 +37,7 @@ function M.serverlist(opts, addrs)
if want_info then
local self_pid = vim.fn.getpid()
local self_active = vim.v.useractive
peers = vim.tbl_map(function(addr) ---@param addr vim.ServerInfo
peers = vim.tbl_map(function(addr)
return {
addr = addr,
pid = self_pid,
+2
View File
@@ -329,6 +329,8 @@ function M.show_msg(tgt, kind, content, replace_last, append, id)
or (api.nvim_buf_line_count(buf) - ((replace_last or cr or append) and 1 or 0))
local curline = (cr or append) and api.nvim_buf_get_lines(buf, row, row + 1, false)[1]
col = mark[2] or (append and not cr and math.min(col, #curline) or 0)
--- EmmyLuaLs/emmylua-analyzer-rust#1269
--- @type integer, integer, integer
local start_row, start_col, lines = row, col, o.lines
local function set_target_pos()
+2 -60
View File
@@ -7,61 +7,6 @@ error('Cannot require a meta file')
---@type uv
vim.uv = ...
--- LuaLS fallback surface for the richer iterator annotations in `vim.iter`.
--- EmmyLua reads the precise generics from `runtime/lua/vim/iter.lua`; LuaLS uses
--- these broader shapes for downstream type-checking.
--- @class vim.Iter
--- @field all fun(self: vim.Iter, pred: fun(...): boolean): boolean
--- @field any fun(self: vim.Iter, pred: fun(...): boolean): boolean
--- @field count fun(self: vim.Iter): integer
--- @field each fun(self: vim.Iter, f: fun(...)): nil
--- @field enumerate fun(self: vim.Iter): vim.Iter
--- @field filter fun(self: vim.Iter, f: fun(...): boolean): vim.Iter
--- @field find fun(self: vim.Iter, f: any): any
--- @field flatten fun(self: vim.Iter, depth?: integer): vim.IterArray
--- @field fold fun(self: vim.Iter, init: any, f: fun(acc: any, ...): any): any
--- @field join fun(self: vim.Iter, delim: string): string
--- @field last fun(self: vim.Iter): any
--- @field map fun(self: vim.Iter, f: fun(...): ...): vim.Iter
--- @field next fun(self: vim.Iter): any
--- @field nth fun(self: vim.Iter, n: integer): any
--- @field peek fun(self: vim.Iter): any
--- @field pop fun(self: vim.Iter): any
--- @field rev fun(self: vim.Iter): vim.IterArray
--- @field rfind fun(self: vim.Iter, f: any): any
--- @field rpeek fun(self: vim.Iter): any
--- @field rskip fun(self: vim.Iter, n: integer): vim.IterArray
--- @field skip fun(self: vim.Iter, n: integer|fun(...): boolean): vim.Iter
--- @field slice fun(self: vim.Iter, first: integer, last: integer): vim.IterArray
--- @field take fun(self: vim.Iter, n: integer|fun(...): boolean): vim.Iter
--- @field totable fun(self: vim.Iter): table
--- @field unique fun(self: vim.Iter, key?: fun(...): any): vim.Iter
--- @class vim.IterArray : vim.Iter
--- @field count fun(self: vim.IterArray): integer
--- @field enumerate fun(self: vim.IterArray): vim.IterArray
--- @field filter fun(self: vim.IterArray, f: fun(...): boolean): vim.IterArray
--- @field find fun(self: vim.IterArray, f: any): any
--- @field flatten fun(self: vim.IterArray, depth?: integer): vim.IterArray
--- @field fold fun(self: vim.IterArray, init: any, f: fun(acc: any, ...): any): any
--- @field last fun(self: vim.IterArray): any
--- @field map fun(self: vim.IterArray, f: fun(...): ...): vim.IterArray
--- @field next fun(self: vim.IterArray): any
--- @field peek fun(self: vim.IterArray): any
--- @field pop fun(self: vim.IterArray): any
--- @field rev fun(self: vim.IterArray): vim.IterArray
--- @field rfind fun(self: vim.IterArray, f: any): any
--- @field rpeek fun(self: vim.IterArray): any
--- @field rskip fun(self: vim.IterArray, n: integer): vim.IterArray
--- @field skip fun(self: vim.IterArray, n: integer|fun(...): boolean): vim.IterArray
--- @field slice fun(self: vim.IterArray, first: integer, last: integer): vim.IterArray
--- @field take fun(self: vim.IterArray, n: integer|fun(...): boolean): vim.IterArray
--- @field totable fun(self: vim.IterArray): table
--- @field unique fun(self: vim.IterArray, key?: fun(...): any): vim.IterArray
--- @class vim.IterModule
--- @operator call: fun(src: any, ...): vim.Iter
--- The following modules are loaded specially in _init_packages.lua
vim.F = require('vim.F')
@@ -75,11 +20,8 @@ vim.func = require('vim.func')
vim.glob = require('vim.glob')
vim.health = require('vim.health')
vim.hl = require('vim.hl')
local iter = require('vim.iter')
-- `require('vim.iter')` carries the richer EmmyLua generic surface. Force
-- LuaLS onto the fallback module shape above so `make luals` stays clean.
---@cast iter vim.IterModule
vim.iter = iter
vim.inspect = require('vim.inspect')
vim.iter = require('vim.iter')
vim.keymap = require('vim.keymap')
vim.loader = require('vim.loader')
vim.log = require('vim.log')
+2
View File
@@ -1,3 +1,5 @@
--- @diagnostic disable: annotation-usage-error
-- Contains filetype detection functions for use in filetype.lua that are either:
-- * used more than once or
-- * complex (e.g. check more than one line or use conditionals).
+2
View File
@@ -144,6 +144,8 @@ local function filepath_to_healthcheck(path)
:find(function(dir)
return vim.fs.relpath(dir, path)
end)
-- The healthcheck path came from nvim_get_runtime_file(), so a runtime directory must match.
assert(rtp_lua)
-- "/path/to/rtp/lua/foo/bar/health.lua" => "foo/bar/health.lua"
-- "/another/rtp/lua/baz/health/init.lua" => "baz/health/init.lua"
local subpath = path:gsub('^' .. vim.pesc(rtp_lua), ''):gsub('^/+', '')
+2 -2
View File
@@ -762,9 +762,9 @@ local function check_sysinfo()
vim.health.start('System Info')
-- Use :version because `vim.version().build` returns "Homebrew" for brew installs.
local version_out = vim.api.nvim_exec2('version', { output = true }).output
local version_out = vim.api.nvim_exec2('version', { output = true }).output --[[@as string]]
local nvim_version = version_out:match('NVIM (v[^\n]+)') or 'unknown'
local commit --[[@type string]] = (version_out:match('%+g(%x+)') or ''):sub(1, 12)
local commit = (version_out:match('%+g(%x+)') or ''):sub(1, 12)
if vim.fn.executable('git') ~= 1 then
vim.health.warn('Cannot check for updates: git not found')
-1
View File
@@ -156,7 +156,6 @@ function M.range(buf, ns, hlgroup, start, finish, opts)
end
end
---@private
---@class (private) vim.hl.OnEventState
---@field timer? uv.uv_timer_t Timer to clear the highlight.
---@field clear? fun() Function to clear the highlight immediately.
+35 -1
View File
@@ -1,4 +1,5 @@
--- @diagnostic disable: no-unknown
--- @class (private) vim.InspectModule
--- @overload fun(root: any, options?: vim.inspect.Opts): string
local inspect = {
_VERSION = 'inspect.lua 3.1.0',
_URL = 'http://github.com/kikito/inspect.lua',
@@ -311,6 +312,7 @@ local function tabify(inspector)
puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level))
end
--- @nodoc
--- @param v any
function Inspector:getId(v)
local id = self.ids[v]
@@ -323,6 +325,7 @@ function Inspector:getId(v)
return tostring(id)
end
--- @nodoc
--- @param v any
function Inspector:putValue(v)
local buf = self.buf
@@ -407,8 +410,31 @@ function Inspector:putValue(v)
end
end
--- @inlinedoc
--- @class vim.inspect.Opts
---
--- Maximum table nesting depth. Deeper tables are shown as `{...}`.
--- (default: unlimited)
--- @field depth? integer
---
--- String used for line breaks.
--- (default: `"\n"`)
--- @field newline? string
---
--- String repeated for each level of indentation.
--- (default: `" "`)
--- @field indent? string
---
--- Transforms values, keys, and metatables before formatting. Return a replacement value,
--- or `nil` to omit a table entry. {path} lists the keys leading to {item}, with
--- `vim.inspect.KEY` appended for keys or `vim.inspect.METATABLE` for metatables.
--- The root has an empty path.
--- @field process? fun(item: any, path: any[]): any
--- @nodoc
--- @param root any
--- @param options? vim.inspect.Opts
--- @return string
function inspect.inspect(root, options)
options = options or {}
@@ -440,6 +466,14 @@ function inspect.inspect(root, options)
end
setmetatable(inspect, {
--- Gets a human-readable representation of the given object.
---
--- @see |vim.print()|
--- @see https://github.com/kikito/inspect.lua
--- @see https://github.com/mpeterv/vinspect
--- @param root any
--- @param options? vim.inspect.Opts
--- @return string
__call = function(_, root, options)
return inspect.inspect(root, options)
end,
+9 -14
View File
@@ -71,7 +71,7 @@
--- @overload fun<T>(src: T[]): vim.IterArray<T, never>
--- @overload fun<K, V>(src: table<K, V>): vim.Iter<K, V>
--- @overload fun(src: table, ...): vim.Iter<any, any...>
--- @overload fun(src: function, ...): vim.Iter<any, any...>
--- @overload fun<S, C, R1, R...>(src: (fun(state: S, control: C): R1, R...), state?: S, control?: C): vim.Iter<R1, R...>
local M = {}
--- @nodoc
@@ -440,7 +440,7 @@ function Iter:each(f)
end
end
--- @private
--- @nodoc
--- @param f fun(v: V1, ...: V...)
function IterArray:each(f)
local inc = self._head < self._tail and 1 or -1
@@ -475,8 +475,6 @@ end
---
---
--- @since 12
--- @overload fun<T>(self: vim.Iter<T, never>): T[]
--- @overload fun<V1, V2, V...>(self: vim.Iter<V1, V2, V...>): [V1, V2, V...][]
--- @return any[]
function Iter:totable()
local t = {}
@@ -493,8 +491,6 @@ function Iter:totable()
end
--- @nodoc
--- @overload fun<T>(self: vim.IterArray<T, never>): T[]
--- @overload fun<V1, V2, V...>(self: vim.IterArray<V1, V2, V...>): [V1, V2, V...][]
--- @return any[]
function IterArray:totable()
if self.next ~= IterArray.next or self._head >= self._tail then
@@ -627,7 +623,7 @@ function Iter:next()
return self._next()
end
--- @package
--- @nodoc
--- @return V1?, V...
function IterArray:next()
if self._head ~= self._tail then
@@ -693,7 +689,7 @@ function Iter:peek()
return unpack(self._peeked)
end
--- @private
--- @nodoc
--- @return V1?, V...
function IterArray:peek()
if self._head ~= self._tail then
@@ -723,8 +719,7 @@ end
---
--- ```
--- @since 12
--- @param f (fun(v: V1, ...: V...): boolean)|any
--- @overload fun<V1, V...>(self: vim.IterArray<V1, V...>, f: V1|fun(v: V1, ...: V...): boolean): V1?, V...
--- @param f V1|fun(v: V1, ...: V...): any
--- @return V1?, V...
function Iter:find(f)
if type(f) ~= 'function' then
@@ -862,7 +857,7 @@ function Iter:take(n)
return self
end
--- @private
--- @nodoc
--- @param n integer|fun(v: V1, ...: V...): boolean
--- @return vim.IterArray<V1, V...>
function IterArray:take(n)
@@ -997,7 +992,7 @@ function Iter:skip(n)
return self
end
--- @private
--- @nodoc
--- @param n integer|fun(v: V1, ...: V...): boolean
--- @return vim.IterArray<V1, V...>
function IterArray:skip(n)
@@ -1185,7 +1180,7 @@ function Iter:last()
return last
end
--- @private
--- @nodoc
--- @return V1?, V...
function IterArray:last()
if self._head == self._tail then
@@ -1236,7 +1231,7 @@ function Iter:enumerate()
end)
end
--- @private
--- @nodoc
--- @return vim.IterArray<integer, V1, V...>
function IterArray:enumerate()
local inc = self._head < self._tail and 1 or -1
+5 -22
View File
@@ -7,7 +7,6 @@ local lsp = vim._defer_require('vim.lsp', {
_changetracking = ..., --- @module 'vim.lsp._changetracking'
_folding_range = ..., --- @module 'vim.lsp._folding_range'
_snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar'
_tagfunc = ..., --- @module 'vim.lsp._tagfunc'
_watchfiles = ..., --- @module 'vim.lsp._watchfiles'
buf = ..., --- @module 'vim.lsp.buf'
client = ..., --- @module 'vim.lsp.client'
@@ -24,6 +23,9 @@ local lsp = vim._defer_require('vim.lsp', {
protocol = ..., --- @module 'vim.lsp.protocol'
rpc = ..., --- @module 'vim.lsp.rpc'
semantic_tokens = ..., --- @module 'vim.lsp.semantic_tokens'
-- Work around EmmyLuaLs/emmylua-analyzer-rust#1238 for function exports.
--- @type fun(pattern: string, flags: string): table[]|vim.NIL
tagfunc = ..., --- @module 'vim.lsp.tagfunc'
util = ..., --- @module 'vim.lsp.util'
})
@@ -396,19 +398,17 @@ lsp.config = setmetatable({ _configs = {} }, {
local function get_config_names()
local config_names = vim
.iter(api.nvim_get_runtime_file('lsp/*.lua', true))
--- @param path string
:map(function(path)
local file_name = path:match('[^/]*.lua$')
local file_name = assert(path:match('[^/]*.lua$'))
return file_name:sub(0, #file_name - 4)
end)
:totable()
:totable() --[[@as string[] ]]
vim.list_extend(config_names, vim.tbl_keys(lsp.config._configs))
return vim
.iter(config_names)
:unique()
--- @param name string
:filter(function(name)
return name ~= '*'
end)
@@ -1443,23 +1443,6 @@ function lsp.formatexpr(opts)
return 0
end
--- Provides an interface between the built-in client and 'tagfunc'.
---
--- When used with normal mode commands (e.g. |CTRL-]|) this will invoke
--- the "textDocument/definition" LSP method to find the tag under the cursor.
--- Otherwise, uses "workspace/symbol". If no results are returned from
--- any LSP servers, falls back to using built-in tags.
---
---@param pattern string Pattern used to find a workspace symbol
---@param flags string See |tag-function|
---
---@return table[]|vim.NIL tags A list of matching tags, or `vim.NIL` to use the built-in tags.
function lsp.tagfunc(pattern, flags)
-- EmmyLua incorrectly treats function exports referenced by @module as non-callable.
--- @diagnostic disable-next-line:call-non-callable EmmyLuaLs/emmylua-analyzer-rust#1238
return vim.lsp._tagfunc(pattern, flags)
end
--- Provides an interface between the built-in client and a `foldexpr` function.
---
--- To use, set 'foldmethod' to "expr" and set the value of 'foldexpr':
+1 -1
View File
@@ -92,7 +92,7 @@ function M:destroy()
api.nvim_del_augroup_by_id(self.augroup)
self.active[self.bufnr] = nil
buf_capabilities[self.bufnr] = vim.tbl_filter(function(cap) ---@param cap vim.lsp.Capability
buf_capabilities[self.bufnr] = vim.tbl_filter(function(cap)
return cap ~= self
end, buf_capabilities[self.bufnr])
+1
View File
@@ -434,6 +434,7 @@ end
--- |lsp-handler| for the method `workspace/codeLens/refresh`
---
---@internal
---@diagnostic disable-next-line: annotation-usage-error
---@type lsp.Handler
function M.on_refresh(err, _, ctx)
if err then
@@ -86,9 +86,17 @@ local function query_workspace_symbols(pattern)
return results
end
---@param pattern string
---@param flags string
---@return table[]|vim.NIL
--- Provides an interface between the built-in client and 'tagfunc'.
---
--- When used with normal mode commands (e.g. |CTRL-]|) this will invoke
--- the "textDocument/definition" LSP method to find the tag under the cursor.
--- Otherwise, uses "workspace/symbol". If no results are returned from
--- any LSP servers, falls back to using built-in tags.
---
---@param pattern string Pattern used to find a workspace symbol
---@param flags string See |tag-function|
---
---@return table[]|vim.NIL tags A list of matching tags, or `vim.NIL` to use the built-in tags.
local function tagfunc(pattern, flags)
-- avoid definition/symbol queries for insert completion
if string.match(flags, 'i') then
+4 -4
View File
@@ -890,7 +890,7 @@ local function install_list(plug_list, confirm)
if not confirm or confirm_install(plug_list) then
trigger_events(plug_list, 'PackChangedPre', 'install')
run_list(plug_list, do_install, 'Installing plugins')
local installed = vim.tbl_filter(function(p) --- @param p vim.pack.Plug
local installed = vim.tbl_filter(function(p)
return p.info.installed == true
end, plug_list)
trigger_events(installed, 'PackChanged', 'install')
@@ -945,7 +945,7 @@ local function infer_update_details(p)
local any_version = vim.version.range('*') --[[@as vim.VersionRange]]
local last_version = get_last_semver_tag(past_tags, any_version)
local newer_semver_tags = vim.tbl_filter(function(x) --- @param x string
local newer_semver_tags = vim.tbl_filter(function(x)
return vim.version.gt(x, last_version)
end, all_semver_tags)
@@ -1400,7 +1400,7 @@ local function update_list(plug_list)
end
run_list(plug_list, do_update, 'Applying updates')
local updated = vim.tbl_filter(function(p) --- @param p vim.pack.Plug
local updated = vim.tbl_filter(function(p)
return p.info.updated == true
end, plug_list)
trigger_events(updated, 'PackChanged', 'update')
@@ -1503,7 +1503,7 @@ function M.update(names, opts)
-- Update and show report
if opts.force then
local plugs_to_update = vim.tbl_filter(function(p) --- @param p vim.pack.Plug
local plugs_to_update = vim.tbl_filter(function(p)
return p.info.sha_head ~= p.info.sha_target
end, plug_list)
update_list(plugs_to_update)
-1
View File
@@ -12,7 +12,6 @@ local M = {}
---@field named vim.treesitter.dev.Node[]
local TSTreeView = {}
---@private
---@class (private) vim.treesitter.dev.TSTreeViewOpts
---@field anon boolean If true, display anonymous nodes.
---@field lang boolean If true, display the language alongside each node.
+19 -23
View File
@@ -347,30 +347,26 @@ do
--- Initialize Progress handlers.
local function progress_init()
progress_group = vim.api.nvim_create_augroup('nvim.ui.progress_status', { clear = true })
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
ev.data.percent = ev.data.percent or 0
progress[ev.data.id] = ev.data
-- Clear finished items
if
ev.data.status == 'success'
or ev.data.percent == 100
or ev.data.status == 'failed'
or ev.data.status == 'cancel'
then
progress[ev.data.id] = nil
end
progress_autocmd = require('vim._core.util').nvim_on('Progress', progress_group, {
desc = 'Tracks progress messages for vim.ui.progress_status()',
}, function(ev)
if not ev.data or not ev.data.id then
return
end
)
--- @cast ev.data vim.event.progress.data
ev.data.percent = ev.data.percent or 0
progress[ev.data.id] = ev.data
-- Clear finished items
if
ev.data.status == 'success'
or ev.data.percent == 100
or ev.data.status == 'failed'
or ev.data.status == 'cancel'
then
progress[ev.data.id] = nil
end
end)
end
--- Gets a status description summarizing currently running progress messages.
+1 -1
View File
@@ -12,7 +12,7 @@
--- end
--- ```
---
--- [vim.version()]() returns the version of the current Nvim process.
--- [vim.version()] returns the version of the current Nvim process.
---
--- VERSION RANGE SPEC [version-range]()
---
+137 -74
View File
@@ -38,7 +38,9 @@ local INDENTATION = 4
--- Generated documentation target, e.g. api.txt
--- @field filename string
---
--- @field section_order string[]
--- Ordered filenames or groups of filenames to merge into one section.
--- For a group, the first filename determines the section name and help tag.
--- @field section_order (string|string[])[]
---
--- List of files/directories for doxygen to read, relative to `base_dir`.
--- @field files string[]
@@ -59,11 +61,6 @@ local INDENTATION = 4
---
--- Per-function helptag.
--- @field fn_helptag_fmt? fun(fun: nvim.gen_vimdoc.HelptagTarget): string
---
--- @field append_only? string[]
---
--- Merge parsed files into the first section instead of rendering one section per file.
--- @field merge_files? boolean
---@alias nvim.gen_vimdoc.HelptagTarget
---| nvim.luacats.parser.fun
@@ -179,9 +176,8 @@ local config = {
-- Sections at the top, in a specific order:
'builtin.lua',
'options.lua',
'editor.lua',
{ 'editor.lua', 'inspect.lua', 'shared.lua' },
'_inspector.lua',
'shared.lua',
-- Sections in alphanumeric order:
'base64.lua',
@@ -233,6 +229,7 @@ local config = {
'runtime/lua/vim/fs.lua',
'runtime/lua/vim/glob.lua',
'runtime/lua/vim/hl.lua',
'runtime/lua/vim/inspect.lua',
'runtime/lua/vim/iter.lua',
'runtime/lua/vim/keymap.lua',
'runtime/lua/vim/loader.lua',
@@ -253,7 +250,7 @@ local config = {
fun.module = 'vim'
end
if fun.module == 'vim' and contains(fun.name, { 'cmd', 'inspect' }) then
if fun.module == 'vim' and fun.name == 'cmd' then
fun.table = nil
end
@@ -312,15 +309,12 @@ local config = {
return fn_helptag_fmt_common(fun)
end,
append_only = {
'shared.lua',
},
},
lsp = {
filename = 'lsp.txt',
section_order = {
-- Sections at the top, in a specific order:
'lsp.lua',
{ 'lsp.lua', 'tagfunc.lua' },
-- Sections in alphanumeric order:
'buf.lua',
@@ -338,7 +332,6 @@ local config = {
'on_type_formatting.lua',
'rpc.lua',
'semantic_tokens.lua',
'tagfunc.lua',
-- Sections at the end, in a specific order:
'util.lua',
@@ -373,9 +366,8 @@ local config = {
async = {
filename = 'lua-async.txt',
section_order = {
'async.lua',
{ 'async.lua', '_core.lua', '_semaphore.lua' },
},
merge_files = true,
files = {
'runtime/lua/vim/async.lua',
'runtime/lua/vim/async/_core.lua',
@@ -388,12 +380,6 @@ local config = {
return { 'lua-async', 'vim.async' }
end,
fn_xform = function(fun)
if fun.module == 'vim.async._core' or fun.module == 'vim.async._semaphore' then
fun.module = 'vim.async'
end
if fun.name == 'new_semaphore' then
fun.name = 'semaphore'
end
if fun.classvar == 'M' then
fun.classvar = nil
end
@@ -425,10 +411,8 @@ local config = {
'highlighter.lua',
'language.lua',
'languagetree.lua',
'query.lua',
'tsquery.lua',
{ 'query.lua', 'tsquery.lua' },
},
append_only = { 'tsquery.lua' },
files = {
'runtime/lua/vim/treesitter/_meta/',
'runtime/lua/vim/treesitter.lua',
@@ -1171,21 +1155,16 @@ local function make_section(filename, cfg, briefs, funs_txt, classes_txt)
end
--- @param section nvim.gen_vimdoc.Section
--- @param add_header? boolean
local function render_section(section, add_header)
local function render_section(section)
local doc = {} --- @type string[]
if not section.title then
error(('section.title is nil, check section_fmt(). section: %s'):format(vim.inspect(section)))
end
if add_header ~= false then
vim.list_extend(doc, {
string.rep('=', TEXT_WIDTH),
'\n',
align_tags(TEXT_WIDTH)(section.title .. ' ' .. section.help_tag),
})
end
doc[#doc + 1] = string.rep('=', TEXT_WIDTH)
.. '\n'
.. align_tags(TEXT_WIDTH)(section.title .. ' ' .. section.help_tag)
if next(section.briefs) then
local briefs_txt = {} --- @type string[]
@@ -1193,23 +1172,21 @@ local function render_section(section, add_header)
briefs_txt[#briefs_txt + 1] = md_to_vimdoc(b, 0, 0, TEXT_WIDTH)
end
local sdoc = '\n\n' .. table.concat(briefs_txt, '\n')
local sdoc = table.concat(briefs_txt, '\n')
if sdoc:find('[^%s]') then
doc[#doc + 1] = sdoc
end
end
if section.classes_txt ~= '' then
table.insert(doc, '\n\n')
table.insert(doc, (section.classes_txt:gsub('\n+$', '\n')))
end
if section.funs_txt ~= '' then
table.insert(doc, '\n\n')
table.insert(doc, section.funs_txt)
end
return table.concat(doc)
return table.concat(doc, '\n\n')
end
local parsers = {
@@ -1243,11 +1220,95 @@ local function find_module_class(classes, modvar)
end
end
--- Add callable exports and inherited module functions under their public names.
--- E.g. `M.semaphore = require('vim.async._semaphore')` in vim.async exposes
--- that module's returned `new_semaphore` function as `vim.async.semaphore()`.
--- A callable table uses its module name: vim.inspect's `__call` => `vim.inspect()`.
--- @param modules table<string,nvim.luacats.parser.module>
local function expand_module_docs(modules)
local exports = {} --- @type table<nvim.luacats.parser.module,string[]>
for _, module in pairs(modules) do
local documented = {} --- @type table<string,true>
for _, fun in ipairs(module.funs) do
if fun.modvar == module.modvar and (not fun.classvar or fun.classvar == module.modvar) then
documented[fun.name] = true
end
end
for name, imported in pairs(module.imports) do
local public_name = module.name .. '.' .. name
if not documented[name] and not ('.' .. public_name):find('%._') then
exports[imported] = exports[imported] or {}
table.insert(exports[imported], public_name)
end
end
end
for name, module in pairs(modules) do
-- E.g. new_semaphore becomes vim.async.semaphore through a require() binding.
-- Otherwise, use the public module path (vim.inspect for its __call).
if module.callable and (exports[module] or not ('.' .. name):find('%._')) then
for _, public_name in ipairs(exports[module] or { name }) do
local fun = vim.deepcopy(module.callable)
fun.module, fun.name = public_name:match('^(.*)%.([^.]+)$')
fun.name = fun.name or public_name
module.funs[#module.funs + 1] = fun
end
end
end
-- `@class vim.async: vim.async._core` makes `vim.async._core.run()` => `vim.async.run()`.
-- Copy inherited functions under the public module name, keeping nearer declarations.
local hidden = {} --- @type table<nvim.luacats.parser.fun,true>
for _, module in pairs(modules) do
if module.class and not ('.' .. module.name):find('%._') then
local names = {} --- @type table<string,true>
local seen = {} --- @type table<nvim.luacats.parser.module,true>
local source = module
while source and not seen[source] do
seen[source] = true
for _, fun in ipairs(source.funs) do
if fun.class == source.class.name and fun.member_sep == '.' then
if
source ~= module
and not names[fun.name]
and not fun.nodoc
and not fun.access
and fun.name:sub(1, 1) ~= '_'
then
local inherited = vim.deepcopy(fun)
inherited.module, inherited.class = module.name, module.class.name
inherited.modvar, inherited.classvar = module.modvar, module.modvar
module.funs[#module.funs + 1] = inherited
end
if source ~= module and ('.' .. source.name):find('%._') then
hidden[fun] = true
end
-- Include functions already inherited by the source module.
names[fun.name] = true
end
end
-- A nearer declaration shadows ancestors, including non-function fields.
for _, field in ipairs(source.class.fields) do
names[field.name] = true
end
for name in pairs(source.assignments) do
names[name] = true
end
source = source.parent
end
end
end
-- Delay hiding until all modules have inherited the original documentation.
for fun in pairs(hidden) do
fun.nodoc = true
end
end
--- @param cfg nvim.gen_vimdoc.Config
local function gen_target(cfg)
cfg.fn_helptag_fmt = cfg.fn_helptag_fmt or fn_helptag_fmt_common
print('Target:', cfg.filename)
local sections = {} --- @type table<string,nvim.gen_vimdoc.Section>
local sections = {} --- @type nvim.gen_vimdoc.Section[]
expand_files(cfg.files)
@@ -1257,20 +1318,28 @@ local function gen_target(cfg)
--- @type table<string,nvim.luacats.parser.class>
local all_classes = {}
--- First pass so we can collect all classes
--- @type table<string,nvim.luacats.parser.module>
local modules = {}
--- First pass so we can collect all classes and module exports.
for _, f in vim.spairs(cfg.files) do
local ext = f:match('%.([^.]+)$')
local parser = parsers[ext]
if parser then
local classes, funs, briefs = parser(f)
local classes, funs, briefs, _, module = parser(f)
file_results[f] = { classes, funs, briefs }
all_classes = vim.tbl_extend('error', all_classes, classes)
if module then
modules[module.name] = module
end
end
end
local merged_classes = {} --- @type table<string,nvim.luacats.parser.class>
local merged_funs = {} --- @type nvim.luacats.parser.fun[]
local merged_briefs = {} --- @type string[]
luacats_parser.resolve_modules(modules)
expand_module_docs(modules)
--- @type table<string,[table<string,nvim.luacats.parser.class>, nvim.luacats.parser.fun[], string[]]>
local files_by_name = {}
for f, r in vim.spairs(file_results) do
local classes, funs, briefs = r[1], r[2], r[3]
@@ -1291,43 +1360,37 @@ local function gen_target(cfg)
print(' Processing file:', f)
if cfg.merge_files then
merged_classes = vim.tbl_extend('error', merged_classes, classes)
vim.list_extend(merged_funs, funs)
vim.list_extend(merged_briefs, briefs)
else
-- FIXME: Using f_base will confuse `_meta/protocol.lua` with `protocol.lua`
local f_base = vim.fs.basename(f)
sections[f_base] = make_section(
f_base,
cfg,
render_briefs(briefs, cfg),
render_funs(funs, all_classes, cfg),
render_classes(classes, funs, cfg)
)
end
-- FIXME: Using basenames will confuse `_meta/protocol.lua` with `protocol.lua`
files_by_name[vim.fs.basename(f)] = r
end
if cfg.merge_files then
local section_file = cfg.section_order[1]
sections[section_file] = make_section(
section_file,
for _, entry in ipairs(cfg.section_order) do
local files = type(entry) == 'table' and entry or { entry }
local classes = {} --- @type table<string,nvim.luacats.parser.class>
local funs = {} --- @type nvim.luacats.parser.fun[]
local briefs = {} --- @type string[]
for _, f in ipairs(files) do
local r = files_by_name[f]
if r then
classes = vim.tbl_extend('error', classes, r[1])
vim.list_extend(funs, r[2])
vim.list_extend(briefs, r[3])
end
end
sections[#sections + 1] = make_section(
files[1],
cfg,
render_briefs(merged_briefs, cfg),
render_funs(merged_funs, all_classes, cfg),
render_classes(merged_classes, merged_funs, cfg)
render_briefs(briefs, cfg),
render_funs(funs, all_classes, cfg),
render_classes(classes, funs, cfg)
)
end
local first_section_tag = sections[cfg.section_order[1]].help_tag
local first_section_tag = sections[1].help_tag
local docs = {} --- @type string[]
for _, f in ipairs(cfg.section_order) do
local section = sections[f]
if section then
print(fmt(" Rendering section: '%s'", section.title))
local add_sep_and_header = not vim.tbl_contains(cfg.append_only or {}, f)
docs[#docs + 1] = render_section(section, add_sep_and_header)
end
for _, section in ipairs(sections) do
print(fmt(" Rendering section: '%s'", section.title))
docs[#docs + 1] = render_section(section)
end
table.insert(
+100 -16
View File
@@ -58,6 +58,17 @@ local luacats_grammar = require('gen.luacats_grammar') * vim.lpeg.Cp()
--- @field last_doc_item? nvim.luacats.parser.param|nvim.luacats.parser.return|nvim.luacats.parser.note
--- @field last_doc_item_indent? integer
--- @class nvim.luacats.parser.module
--- @field name string Module name inferred from the path, used to resolve require().
--- @field modvar? string
--- @field class? nvim.luacats.parser.class Class of the returned module table.
--- @field requires table<string,string> Module fields assigned directly from require(), keyed by field name.
--- @field imports table<string,nvim.luacats.parser.module> Bindings linked by resolve_modules().
--- @field parent? nvim.luacats.parser.module Module whose returned class is this module class's parent.
--- @field callable? nvim.luacats.parser.fun Returned function or the returned table's __call.
--- @field assignments table<string,true> Field assignments (`M.name = ...`), including unannotated ones.
--- @field funs nvim.luacats.parser.fun[]
--- @alias nvim.luacats.parser.obj
--- | nvim.luacats.parser.class
--- | nvim.luacats.parser.fun
@@ -409,20 +420,9 @@ end
local function determine_modvar(str)
local modvar --- @type string?
for line in vim.gsplit(str, '\n') do
do
--- @type string?
local m = line:match('^return%s+([a-zA-Z_]+)')
if m then
modvar = m
end
end
do
--- @type string?
local m = line:match('^return%s+setmetatable%(([a-zA-Z_]+),')
if m then
modvar = m
end
end
modvar = line:match('^return%s+setmetatable%s*%(%s*([%w_]+)%s*,')
or line:match('^return%s+([%w_]+)')
or modvar
end
return modvar
end
@@ -495,6 +495,16 @@ function M.parse_str(str, filename)
local module = filename:match('.*/lua/([a-z_][a-z0-9_/]+)%.lua') or filename
module = module:gsub('/', '.')
--- @type nvim.luacats.parser.module
local module_info = {
name = module,
modvar = mod_return,
requires = {},
imports = {},
assignments = {},
funs = funs,
}
local classvars = {} --- @type table<string,string>
local state = {} --- @type nvim.luacats.parser.State
@@ -503,12 +513,20 @@ function M.parse_str(str, filename)
local uncommitted = {} --- @type nvim.luacats.parser.obj[]
local comments = {}
local in_module_metatable = false
for line in vim.gsplit(str, '\n') do
local has_indent = line:match('^%s+') ~= nil
line = vim.trim(line)
if vim.startswith(line, '---') then
comments[#comments + 1] = use_type_alt(line:sub(4):gsub('^%s+@', '@'))
else
-- Recognize the returned module's inline metatable at file scope.
if not has_indent and line ~= '' and not vim.startswith(line, '--') then
local target = line:match('^setmetatable%s*%(%s*([%w_]+)%s*,%s*{')
or line:match('^return%s+setmetatable%s*%(%s*([%w_]+)%s*,%s*{')
in_module_metatable = target ~= nil and target == mod_return
end
if #comments > 0 then
local comment = table.concat(comments, '\n') .. '\n'
local pos = 1
@@ -523,12 +541,36 @@ function M.parse_str(str, filename)
end
add_doc_lines_to_obj(state)
if not has_indent then
local parent, name, value = line:match('^([%w_]+)%.([%w_]+)%s*=%s*(.*)$')
if parent == mod_return and name then
module_info.assignments[name] = true
local _, required, tail = value:match([[^require%s*%(%s*(['"])([^'"]+)%1%s*%)%s*(.*)$]])
module_info.requires[name] = required and (tail == '' or tail:match('^%-%-')) and required
or nil
end
end
if state.cur_obj then
state.cur_obj.modvar = mod_return
state.cur_obj.module = module
end
process_lua_line(line, state, classes, classvars, has_indent)
-- Keep the callable declaration separate from the table's member functions.
local returned = line:match('^local%s+function%s+([%w_]+)%s*%(')
local callable_name = returned
if in_module_metatable and line:match('^__call%s*=%s*function%s*%(') then
in_module_metatable = false
returned = mod_return
callable_name = '__call'
end
if returned and returned == mod_return and state.cur_obj then
state.cur_obj.name = callable_name
module_info.callable = state.cur_obj
state.cur_obj = nil
else
process_lua_line(line, state, classes, classvars, has_indent)
end
-- Commit the object
local cur_obj = state.cur_obj
@@ -543,9 +585,51 @@ function M.parse_str(str, filename)
end
end
if mod_return then
module_info.class = classes[classvars[mod_return]]
end
-- dump_uncommitted(filename, uncommitted)
return classes, funs, briefs, uncommitted
return classes, funs, briefs, uncommitted, module_info
end
--- Connect separately parsed modules by filling their `imports` and `parent` fields.
--- Call after parsing all input files, passing their module records keyed by module name.
---
--- For example, the Lua source file pkg/child.lua contains:
--- ```lua
--- --- @class Child: Base
--- local M = {}
--- M.factory = require('pkg.factory')
--- return M
--- ```
--- The caller stores the parsed record for this file in `modules['pkg.child']`.
--- That record describes M, the table returned by the source file:
--- - `requires.factory` contains the string 'pkg.factory'. This function fills
--- `imports.factory` with `modules['pkg.factory']`, the parsed record for that file.
--- - `class.parent` contains the string 'Base'. This function fills `parent` with
--- the parsed record of the module whose returned table is annotated `@class Base`.
---
--- Each link points to the original parsed module; a missing target leaves it nil.
--- The string names in `requires` and `class.parent` are retained. No declarations
--- are copied, renamed or hidden.
--- @param modules table<string,nvim.luacats.parser.module> Parsed modules keyed by require path.
function M.resolve_modules(modules)
local by_class = {} --- @type table<string,nvim.luacats.parser.module>
for _, module in pairs(modules) do
if module.class then
by_class[module.class.name] = module
end
end
for _, module in pairs(modules) do
module.imports = {}
for name, required in pairs(module.requires) do
module.imports[name] = modules[required]
end
module.parent = module.class and by_class[module.class.parent] or nil
end
end
--- @param filename string
+96 -1
View File
@@ -143,6 +143,100 @@ describe('luacats parser', function()
eq({ { type = 'boolean', desc = 'Whether the value is nil.' } }, funs[1].returns)
end)
it('links require bindings without renaming declarations', function()
local _, _, _, _, consumer = parser.parse_str(
dedent([[
local M = {}
--- A binding with its own documentation.
M.create = require('vim.factory')
M.missing = require('vim.missing')
return M
]]),
'runtime/lua/vim/_consumer.lua'
)
local _, funs, _, _, implementation = parser.parse_str(
dedent([[
--- Create a value.
--- @param value string
--- @return string
local function new_value(value) end
return new_value
]]),
'runtime/lua/vim/factory.lua'
)
parser.resolve_modules({ ['vim._consumer'] = consumer, ['vim.factory'] = implementation })
-- Check table identity: the import must reference the original parsed module.
eq(true, implementation == consumer.imports.create)
eq('vim.missing', consumer.requires.missing)
eq(nil, consumer.imports.missing)
eq({}, funs)
eq('new_value', implementation.callable.name)
eq('vim.factory', implementation.callable.module)
eq('Create a value.', implementation.callable.desc)
eq({ { name = 'value', type = 'string' } }, implementation.callable.params)
eq({ { type = 'string' } }, implementation.callable.returns)
end)
it('keeps a callable module declaration separate from its members', function()
local _, funs, _, _, module = parser.parse_str(
dedent([[
local M = {}
setmetatable(M, {
--- Format a value.
--- @param value any
--- @return string
__call = function(_, value) end,
})
return M
]]),
'runtime/lua/vim/example.lua'
)
eq({}, funs)
eq('__call', module.callable.name)
eq('vim.example', module.callable.module)
eq('Format a value.', module.callable.desc)
eq({ { name = 'value', type = 'any' } }, module.callable.params)
eq({ { type = 'string' } }, module.callable.returns)
end)
it('links table imports and parent modules without copying or hiding members', function()
local _, core_funs, _, _, core = parser.parse_str(
dedent([[
--- @class vim._core
local Core = {}
--- Shared function.
--- @return string
function Core.create() end
return Core
]]),
'runtime/lua/vim/_core.lua'
)
local _, funs, _, _, public = parser.parse_str(
dedent([[
--- @class vim.public: vim._core
local M = {}
M._core = require('vim._core')
M.create = false
return M
]]),
'runtime/lua/vim/public.lua'
)
parser.resolve_modules({ ['vim._core'] = core, ['vim.public'] = public })
-- Check table identity: both links must reference the original parsed module.
eq(true, core == public.imports._core)
eq(true, core == public.parent)
eq(true, public.assignments.create)
eq({}, funs)
eq('create', core_funs[1].name)
eq('vim._core', core_funs[1].module)
eq('Shared function.', core_funs[1].desc)
eq({ { type = 'string' } }, core_funs[1].returns)
eq(nil, core_funs[1].nodoc)
end)
it('parses multiline return annotations', function()
local _, funs = parser.parse_str(
dedent([[
@@ -168,7 +262,8 @@ describe('luacats parser', function()
it('tracks class member declaration style', function()
local classes, funs = parser.parse_str(
dedent([[ --- @class vim.MyClass
dedent([[
--- @class vim.MyClass
local MyClass = {}
--- Dot member.