Problem: Installing plugin always pulls latest `version` changes
(usually from the default branch or "latest version tag"). It is more
robust to prefer initial installation to use the latest recorded
(i.e. "working") revision.
Solution: Prefer using revision from the lockfile (if present) during
install. The extra `update()` will pull the latest changes.
Problem: Running `update()` by default doesn't include not active
plugins, because there was no way to get relevant `version` to get
updates from. This might be a problem in presence of lazy loaded
plugins, i.e. ones that can be "not *yet* active" but still needed to
be updated.
Solution: Include not active plugins by default since their `version` is
tracked via lockfile.
Problem: The revision data is returned behind `opts.info` flag because
it required extra Git calls. With lockfile it is not the case.
Solution: Use lockfile to always set `rev` field in output of `get()`.
Problem: `get()` doesn't return `spec.version` about not-yet-active
plugins (because there was no way to know that without `add()`).
Solution: Use lockfile data to set `spec.version` of non-active plugins.
Problem: Some use cases require or benefit from persistent on disk
storage of plugin data (a.k.a. "lockfile"):
1. Allow `update()` to act on not-yet-active plugins. Currently if
`add()` is not yet called, then plugin's version is unknown
and `update()` can't decide where to look for changes.
2. Efficiently know plugin's dependencies without having to read
'pkg.json' files on every load for every plugin. This is for the
future, after there is `packspec` support (or other declaration of
dependencies on plugin's side).
3. Allow initial install to check out the exact latest "working" state
for a reproducible setup. Currently it pulls the latest available
`version.`
4. Ensure that all declared plugins are installed, even if lazy loaded.
So that later `add()` does not trigger auto-install (when there
might be no Internet connection, for example) and there is no issues
with knowing which plugins are used in the config (so even never
loaded rare plugins are still installed and can be updated).
5. Allow `add()` to detect if plugin's spec has changed between
Nvim sessions and act accordingly. I.e. either set new `src` as
origin or enforce `version.` This is not critical and can be done
during `update()`, but it might be nice to have.
Solution: Add lockfile in JSON format that tracks (adds, updtes,
removes) necessary data for described use cases. Here are the required
data that enables each point:
1. `name` -> `version` map.
2. `name` -> `dependencies` map.
3. `name` -> `rev` map. Probably also requires `name` -> `src` map
to ensure that commit comes from correct origin.
4. `name` -> `src` map. It would be good to also track the order,
but that might be too many complications and redundant together
with point 2.
5. Map from `name` to all relevant spec fields. I.e. `name` -> `src`
and `name` -> `version` for now. Storing data might be too much,
but can be discussed, of course.
This commit only adds lockfile tracking without implementing actual
use cases. It is stored in user's config directory and is suggested to
be tracked via version control.
Example of a lockfile:
```json
{
# Extra nesting to more future proof.
"plugins": {
"plug-a": {
"ref": "abcdef1"
"src": "https://github.com/user/plug-a",
# No `version` means it was `nil` (infer default branch later)
},
"plug-b": {
"dependencies": ["plugin-a", "plug-c"],
"src": "https://github.com/user/plug-b",
"ref": "bcdefg2",
# Enclose string `version` in quotes
"version": "'dev'"
},
"plug-c": {
"src": "https://github.com/user/plug-c",
"ref": "cdefgh3",
# Store `vim.version.Range` via its `tostring()` output
"version": ">=0.0.0",
}
}
}
```
Use only a single clear() call in some test/functional/vimscript/ test
files whose test cases have very little side effect.
A downside of using a single clear() is that if a crash happens in one
test case, all following test cases in the same file will also fail, but
these functionalities and tests don't change very often.
Problem:
If there are 2 language servers with different trigger chars (`-` and
`>`), and a keymap inputs both simultaneously (`->`), then `>` doesn't
trigger. We get completion items from server1 only.
This happens because the `completion_timer` for the `-` trigger is still
pending.
Solution:
If the next character arrived enough quickly (< 25 ms), replace the
existing deferred autotrigger with a new one that matches this later
character.
Overriding vim.lsp.handlers['textDocument/formatting'] doesn't work here
because fake_lsp_server_setup() uses a table with __index to specify
client handlers, which takes priority over vim.lsp.handlers[], and as a
result the overridden handler is never called, and the test ends before
the vim.wait() even finishes.
Instead, set a global variable from the handler that is actually reached
(by vim.rpcrequest() from client handler), and avoid stopping the event
loop too early.
This also fixes the following warning in tests with ASAN or TSAN:
-------- Running tests from test/functional/plugin/lsp/inline_completion_spec.lua
RUN T4604 vim.lsp.inline_completion enable() requests or abort when entered/left insert mode: 225.00 ms OK
RUN T4605 vim.lsp.inline_completion get() applies the current candidate: 212.00 ms OK
nvim took 2013 milliseconds to exit after last test
This indicates a likely problem with the test even if it passed!
RUN T4606 vim.lsp.inline_completion get() accepts on_accept callback: 212.00 ms OK
RUN T4607 vim.lsp.inline_completion select() selects the next candidate: 220.00 ms OK
-------- 4 tests from test/functional/plugin/lsp/inline_completion_spec.lua (3437.00 ms total)
-------- Running tests from test/functional/plugin/lsp/linked_editing_range_spec.lua
nvim took 2011 milliseconds to exit after last test
This indicates a likely problem with the test even if it passed!
Problem:
LSP logs show misleading "cannot start" messages when editing a filetype
NOT listed in the `config.filetypes` field.
[ERROR][2025-09-13 18:55:56] …/runtime//lua/vim/lsp/log.lua:151
"cannot start cssls due to config error: …/runtime//lua/vim/lsp.lua:423:
cmd: expected expected function or table with executable command,
got table: 0x0104701b18. Info: vscode-css-language-server is not executable"
Solution:
- `can_start`: check `config.filetypes` before checking the rest of the
config.
Problem: First clean start with config containing multiple
`vim.pack.add()` calls requires to explicitly confirm each one.
Although usually a rare occurrence, it still might be tedious.
Solution: Add a third choice during installation confirmation that
approves current and all next installs within current session. It is
reset after session restart.
The flakiness happens because get() uses vim.schedule(), and a following
key may be processed before the scheduled event. Use poke_eventloop() to
ensure that the scheduled event is processed.
Problem: Some environment variables which are useful when working inside
a bare repository can affect any Git operation.
Solution: Explicitly unset problematic environment variables.
Problem:
Force resolve `spec.version` overrides the information about whether
a user supplied `version` or not. Knowing it might be useful in some use
cases (like comparing to previously set `spec` to detect if it has
changed).
Solution:
Do not resolve `spec.version`. This also improves speed when triggering
events and calling `get()`.
- Place default branch first when listing all branches.
- Use correct terminology in `get_hash` helper.
- Do not return `{ '' }` if there are no tags.
Problem:
There is no way to get more information about installed plugins, like
current revision or default branch (necessary if resolving default
`spec.version` manually). As computing Git data migth take some time,
also allow `get()` to limit output to only necessary set of plugins.
Solution:
- introduce arguments to `get(names, opts)`, which follows other
`vim.pack` functions. Plugin extra info is returned by default and
should be opt-out via `opts.info = false`.
- Examples:
- Get current revision: `get({ 'plug-name' })[1].rev`
- Get default branch: `get({ 'plug_name' })[1].branches[1]`
- `update()` and `del()` act on plugins in the same order their names
are supplied. This is less surprising.
- default `opts.info` to `true` since this simplifies logic for the
common user, while still leaving the door open for a faster `get()` if
needed.
Problem: Progress reports use plain `nvim_echo()` with manually
constructed messages and populate history on every call.
Solution: Use `nvim_echo()` with newly added `kind=progress` which (at
least for now) is meant to be a unified interface for showing progress
report. Also save in history only first and last progress report
messages.
Problem:
Hover response of MarkedString[] where the first element contains a
language identifier treated as empty.
Solution:
Fix empty check to handle case of MarkedString[] where the first element
is a pair of a language and value.
Problem:
The load function in opts was difficult to use if you wished to
customize based on the plugin being loaded.
You could get the name, but without some way to mark a spec, that was of
limited usefulness unless you wanted to hardcode a list of names in the
function, or write a wrapper around the whole thing
Solution:
Allow users to provide an arbitrary data field in plugin specs so that
they may receive info as to how to handle that plugin in load, get() and
events, and act upon it
Co-authored-by: BirdeeHub <birdee@localhost>
Co-authored-by: Evgeni Chasnovski <evgeni.chasnovski@gmail.com>
Problem:
Buffer-updates on_detach callback is invoked before buf_freeall(), which
deletes autocmds of the buffer (via apply_autocmds(EVENT_BUFWIPEOUT,
...)). Due to this, buffer-local autocmds executed in on_detach (e.g.,
LspDetach) are not actually invoked.
Solution:
Call buf_updates_unload() before buf_freeall().
Problem: No way to skip install confirmation in `add()`. Having install
confirmation by default is a more secure design. However, users are
usually aware of the fact that plugin will be installed and there is
currently no way to skip confirmation.
Plus it can introduce inconvenience on the clean config initialization
if it is modularized with many `vim.pack.add()` calls (leads to
confirming installation many times in a row).
Solution: Add `opts.confirm` option that can skip install confirmation.
Problem: No way to have full control over how plugin is loaded.
Although `:packadd!` has small side effects (only adds plugin
directory to 'runtimepath'; and maybe its 'after/' subdirectory), it
still has side effects. For example, 'plugin/' directories are still
loaded during startup (as part of `:h load-plugins`).
Solution: Allow function `opts.load` that has full control over how
plugin is loaded.
Problem: the `load=true` in `vim.pack.add()` means that `:packadd` is
executed even during startup. This leads to force source of 'plugin/',
which breaks the intended loading order (`:h load-plugins`) and
results into sourcing them twice. This also makes it ignore
`--noplugin` argument.
Using `:packadd!` during startup is more appropriate, while `:packadd`
afterwards is still more favorable to actually force 'plugin/' source
(as there is no pre-defined mechanism that will load them later).
Solution: have `load=false` default during startup, `true` - afterwards.
- Add delimiter between function signature and documentation, matching hover formatting
- Show title only if there are multiple clients or multiple signatures
- Avoid duplicating the title inside the window if it's already shown in the border
Problem: make_floating_popup_options only shows when opts.border is explicitly set, ignoring global winborder setting
Solution: check both opts.border and vim.o.winborder when determining whether to show title
The cursor movement autocommand can not detect when the final tabstop $0
is directly adjacent to another tabstop, which prevents ending the
snippet session. The fix is an early return when jumping.
Problem:
Previously, 'null' value in LSP responses were decoded as 'nil'.
This caused ambiguity for fields typed as '? | null' and led to
loss of explicit 'null' values, particularly in 'data' parameters.
Solution:
Decode all JSON 'null' values as 'vim.NIL' and adjust handling
where needed. This better aligns with the LSP specification,
where 'null' and absent fields are distinct, and 'null' should
not be used to represent missing values.
This also enables proper validation of response messages to
ensure that exactly one of 'result' or 'error' is present, as
required by the JSON-RPC specification.
**Problem:** For unchanged document diagnostic reports, the `resultId`
is ignored completely, even though it should still be saved for the
request (in fact, the spec marks it as mandatory for unchanged reports,
so it should be extra important).
**Solution:** Always store the `resultId`.
Problem:
From https://matrix.to/#/!cylwlNXSwagQmZSkzs:matrix.org/$Ofj-TFIsEMbp0O9OhE8xuZSNi-nhRLtZTOgs6JRLNrs?via=matrix.org&via=gitter.im&via=mozilla.org
In lesson 2.6, users are asked to remove the second, forth and fifth
lines with `dd` command, then they are asked to undo twice to make the
text go back to original state. But after that, the mark ✗ appears
again, which confuses the user because they think they do something
wrong. This is a limitation with the current implementation, which is
based on line number only.
Solution:
Reimplement interactive marks as extmarks in Lua. This also make the
feature less fragile, as users can remove, add some arbitrary lines
without breaking the interactive marks.
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
Problem: Health check floating window gets closed when pressing 'gO' to show TOC because LSP floating preview system auto-closes on BufEnter events triggered by :lopen.
Solution: Temporarily disable BufEnter event for the current window during TOC operations and adjust window layout to prevent overlap.