Problem: cannot perform autocompletion
Solution: Add the 'autocomplete' option value
(Girish Palya)
This change introduces the 'autocomplete' ('ac') boolean option to
enable automatic popup menu completion during insert mode. When enabled,
Vim shows a completion menu as you type, similar to pressing |i\_CTRL-N|
manually. The items are collected from sources defined in the
'complete' option.
To ensure responsiveness, this feature uses a time-sliced strategy:
- Sources earlier in the 'complete' list are given more time.
- If a source exceeds its allocated timeout, it is interrupted.
- The next source is then started with a reduced timeout (exponentially
decayed).
- A small minimum ensures every source still gets a brief chance to
contribute.
The feature is fully compatible with other |i_CTRL-X| completion modes,
which can temporarily suspend automatic completion when triggered.
See :help 'autocomplete' and :help ins-autocompletion for more details.
To try it out, use :set ac
You should see a popup menu appear automatically with suggestions. This
works seamlessly across:
- Large files (multi-gigabyte size)
- Massive codebases (:argadd thousands of .c or .h files)
- Large dictionaries via the `k` option
- Slow or blocking LSP servers or user-defined 'completefunc'
Despite potential slowness in sources, the menu remains fast,
responsive, and useful.
Compatibility: This mode is fully compatible with existing completion
methods. You can still invoke any CTRL-X based completion (e.g.,
CTRL-X CTRL-F for filenames) at any time (CTRL-X temporarily
suspends 'autocomplete'). To specifically use i_CTRL-N, dismiss the
current popup by pressing CTRL-E first.
---
How it works
To keep completion snappy under all conditions, autocompletion uses a
decaying time-sliced algorithm:
- Starts with an initial timeout (80ms).
- If a source does not complete within the timeout, it's interrupted and
the timeout is halved for the next source.
- This continues recursively until a minimum timeout (5ms) is reached.
- All sources are given a chance, but slower ones are de-prioritized
quickly.
Most of the time, matches are computed well within the initial window.
---
Implementation details
- Completion logic is mostly triggered in `edit.c` and handled in
insexpand.c.
- Uses existing inc_compl_check_keys() mechanism, so no new polling
hooks are needed.
- The completion system already checks for user input periodically; it
now also checks for timer expiry.
---
Design notes
- The menu doesn't continuously update after it's shown to prevent
visual distraction (due to resizing) and ensure the internal list
stays synchronized with the displayed menu.
- The 'complete' option determines priority—sources listed earlier get
more time.
- The exponential time-decay mechanism prevents indefinite collection,
contributing to low CPU usage and a minimal memory footprint.
- Timeout values are intentionally not configurable—this system is
optimized to "just work" out of the box. If autocompletion feels slow,
it typically indicates a deeper performance bottleneck (e.g., a slow
custom function not using `complete_check()`) rather than a
configuration issue.
---
Performance
Based on testing, the total roundtrip time for completion is generally
under 200ms. For common usage, it often responds in under 50ms on an
average laptop, which falls within the "feels instantaneous" category
(sub-100ms) for perceived user experience.
| Upper Bound (ms) | Perceived UX
|----------------- |-------------
| <100 ms | Excellent; instantaneous
| <200 ms | Good; snappy
| >300 ms | Noticeable lag
| >500 ms | Sluggish/Broken
---
Why this belongs in core:
- Minimal and focused implementation, tightly integrated with existing
Insert-mode completion logic.
- Zero reliance on autocommands and external scripting.
- Makes full use of Vim’s highly composable 'complete' infrastructure
while avoiding the complexity of plugin-based solutions.
- Gives users C native autocompletion with excellent responsiveness and
no configuration overhead.
- Adds a key UX functionality in a simple, performant, and Vim-like way.
closes: vim/vim#17812af9a7a04f1
Co-authored-by: Girish Palya <girishji@gmail.com>
Problem:
Some LSPs cause the following completion error (reformatted slightly):
Error executing vim.schedule lua callback:
.../runtime/lua/vim/lsp/completion.lua:373
attempt to index field 'range' (a nil value)
This is because an internal function assumes edits are either missing
or of type `TextEdit`, but there's a third [possibility][0] that's not
handled: the `InsertReplaceEdit`.
This was previously reported in at least two issues:
- https://github.com/neovim/neovim/issues/33142
- https://github.com/neovim/neovim/issues/33224
Solution:
Don't assume the edit is a `TextEdit`. This implicitly handles
`InsertReplaceEdit`s.
Also, add a test case for this, which previously caused an error.
[0]: 2c07428966/runtime/lua/vim/lsp/_meta/protocol.lua (L1099)
problem: Error notifications from LSP responses were difficult to read due to
inconsistent formatting and missing critical information like client name and error codes.
solution: Implemented a more structured error notification format using pipe separators to
clearly display client name, error code (when available), and error message.
Problem: `trigger` is a custom word not yet used in APIs.
Solution: Use `get` instead because the main effect is that the
completion candidates will be collected (and shown by default,
but follow-up commits are planned to add an `on_result` callback
that allows more general handling).
---------
Co-authored-by: Christian Clason <c.clason@uni-graz.at>
Problem:
vim.lsp.completion with "autotrigger" enabled, does not send
completion context, even though it has all the necessary info.
Solution:
Include the context for "autotrigger".
trigger() also optionally accepts context when manually invoked.
Problem:
After https://github.com/neovim/neovim/pull/32377 selecting snippets
provided by luals inserted the multi-line text before accepting the
candidates. That's inconsistent with servers who provide `textEdit`
instead of `insertText` and having lines shift up/down while cycling
through the completion candidates is a bit irritating.
Solution:
Use the logic used for `textEdit` snippets also for `insertText`
`command` was already resolved via a `completionItem/resolve` request
but only if `additionalTextEdits` were also present, and the
`resolveSupport` capability wasn't listed.
Closes https://github.com/neovim/neovim/issues/32406
Problem: When expanding a completion item that contains a multi-line word, the word is not deleted correctly.
Solution: If the word contains a line break, delete the text from Context.cursor to the current cursor position.
Problem: autotrigger option of vim.lsp.completion.enable() would trigger
all clients, as long as it matched at least one client's
triggerCharacters.
Solution: trigger only the clients with triggerCharacters matching the
character. overtriggering still happens if any client returns
isIncomplete=true (this case is more involved).
Co-authored-by: Mathias Fussenegger <f.mathias@zignar.net>
Problem:
vim.lsp.completion.enable(true, client.id, bufnr)
vim.lsp.completion.enable(false, client.id, bufnr)
Error detected while processing LspDetach Autocommands for "*":
Error executing lua callback: …/lsp/completion.lua:701: Vim:E367: No such group: "vim/lsp/completion-22"
stack traceback:
[C]: in function 'nvim_del_augroup_by_name'
…/lsp/completion.lua:701: in function 'disable_completions'
…/lsp/completion.lua:724: in function 'enable'
Solution:
Delete the correct augroup.
Follow up to https://github.com/neovim/neovim/pull/32072
If there is no prefix (e.g. at the start of word boundary or a line), it
always used the `filterText` because the `match` function always
returned false.
Problem:
With language servers like lemminx, completing xml tags like `<mo` first
shows the right candidates (`modules`) but after typing `d` the
candidates disappear.
This is because the server returns:
[...]
filterText = "<module",
label = "module",
textEdit = {
newText = "<module>$1</module>$0",
Which resulted in `module` being used as `word`, and `module` doesn't
match the prefix `<mo`. Typing `d` causes the `complete()` filtering
mechanism to kick in and remove the entry.
Solution:
Use `<module` from the `filterText` as `word` if the textEdit/label
heuristic doesn't match.
Problem:
LSP spec uses the term "position encoding" where we say "offset encoding".
Solution:
- Rename it everywhere except `vim.lsp.Client.offset_encoding` (which would be breaking).
- Mention "position encoding" in the documentation for `vim.lsp.Client.offset_encoding`.
* deprecate old signatures
* move to new str_byteindex/str_utfindex signature
* use single-underscore name (double-underscore is reserved for Lua itself)
Problem: on `CompleteDone` cursor can jump to the end of line instead of
the end of the completed word.
Solution: remove only inserted word for snippet expansion instead of everything
until eol.
Fixes#30656
Co-authored-by: Mathias Fussenegger <f.mathias@zignar.net>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
Problem: Currently completion attribute hl_group is combined with
all items, which is redundant and confusing with kind_hlgroup
Solution: Renamed to abbr_hlgroup and combine it only with the abbr item
(glepnir).
closes: vim/vim#158180fe17f8ffb
Co-authored-by: glepnir <glephunter@gmail.com>
Problem: prefix can be a symbol like period, the fuzzy matching can't
handle it correctly.
Solution: when prefix is empty or a symbol add all lsp completion
result into matches.
Problem: Some items of completion results include function signatures that can
cause the pum to be very long when a function has many params, because pum
scales with the longest word/abbr.
Solution: add custom covert function that can customise abbr to remove params.
Problem: the autotrigger mechanism could fire completion requests despite
completion already being active from another completion mechanism or manual
trigger
Solution: add a condition to avoid an additional request.
Problem: CompletionItem in lsp spec mentioned the deprecated attribute
Solution: when item has deprecated attribute set hl_group to DiagnosticDeprecated
in complete function
Although the built-in pum completion mechanism will filter anyway on the
next input it is odd if the initial popup shows entries which don't
match the current prefix.
Using fuzzy match on the label/prefix is compatible with
`completeopt+=fuzzy` and also doesn't seem to break postfix snippet
cases
Closes https://github.com/neovim/neovim/issues/29287
Problem:
For snippets lsp.completion prefers the label if it is shorter than the
insertText or textEdit to support postfix completion cases but clangd
adds decoration characters to labels. E.g.: `•INT16_C(c)`
Solution:
Use parse_snippet on insertText/textEdit before checking if it is
shorter than the label.
Fixes https://github.com/neovim/neovim/issues/29301
Problem: Completion side effects not working randomly.
Solution: When creating the table of LSP responses, the table index
was used, but this is not the same as the actual client_id, so it was changed
to use the client_id directly.
The `complete()` mechanism matches completion candidates against
the typed text, so strict pre-filtering isn't necessary.
This is a first step towards supporting postfix snippets (like
`items@insert` in luals)