diff --git a/bundle/nvim-cmp/.github/FUNDING.yml b/bundle/nvim-cmp/.github/FUNDING.yml
deleted file mode 100644
index ccdeccc61..000000000
--- a/bundle/nvim-cmp/.github/FUNDING.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-# These are supported funding model platforms
-
-github: [hrsh7th]
diff --git a/bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.yml b/bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.yml
index 2b24357df..4fc4a2c5f 100644
--- a/bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -7,24 +7,17 @@ body:
attributes:
label: FAQ
options:
- - label: I have checked the [FAQ](https://github.com/hrsh7th/nvim-cmp/blob/15f08a8faa22d52480cdcb9ef9ca698120f04363/doc/cmp.txt#L616) and it didn't resolve my problem.
+ - label: I have checked the [FAQ](https://github.com/hrsh7th/nvim-cmp/blob/main/doc/cmp.txt) and it didn't resolve my problem.
required: true
- type: checkboxes
- id: issue-prerequisite
+ id: announcement-prerequisite
attributes:
- label: Issues
+ label: Announcement
options:
- - label: I have checked [existing issues](https://github.com/hrsh7th/nvim-cmp/issues) and there are no open or closed issues with the same problem.
+ - label: I have checked [Breaking change announcement](https://github.com/hrsh7th/nvim-cmp/issues/231).
required: true
- - type: input
- attributes:
- label: "Neovim Version"
- description: "`nvim --version`:"
- validations:
- required: true
-
- type: textarea
attributes:
label: "Minimal reproducible full config"
@@ -34,6 +27,10 @@ body:
2. Edit `~/cmp-repro.vim` for reproducing the issue
3. Open `nvim -u ~/cmp-repro.vim`
4. Check reproduction step
+ value: |
+ ```vim
+
+ ```
validations:
required: true
diff --git a/bundle/nvim-cmp/.github/workflows/format.yaml b/bundle/nvim-cmp/.github/workflows/format.yaml
new file mode 100644
index 000000000..8e5c4a293
--- /dev/null
+++ b/bundle/nvim-cmp/.github/workflows/format.yaml
@@ -0,0 +1,25 @@
+name: format
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - '**.lua'
+
+jobs:
+ postprocessing:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Format with Stylua
+ uses: JohnnyMorganz/stylua-action@v2
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ version: v0.16.1
+ args: ./lua
+
+ - uses: stefanzweifel/git-auto-commit-action@v4
+ with:
+ commit_message: "Format with stylua"
diff --git a/bundle/nvim-cmp/.github/workflows/integration.yaml b/bundle/nvim-cmp/.github/workflows/integration.yaml
index 4bac4baca..3bf658fa0 100644
--- a/bundle/nvim-cmp/.github/workflows/integration.yaml
+++ b/bundle/nvim-cmp/.github/workflows/integration.yaml
@@ -16,13 +16,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- - name: Setup rust
- uses: actions-rs/toolchain@v1
- with:
- toolchain: nightly
- default: true
- override: true
-
- name: Setup neovim
uses: rhysd/action-setup-vim@v1
with:
@@ -40,12 +33,9 @@ jobs:
- name: Setup tools
shell: bash
run: |
- sudo apt install -y curl unzip --no-install-recommends
- bash ./utils/install_stylua.sh
luarocks install luacheck
luarocks install vusted
- name: Run tests
shell: bash
run: make integration
-
diff --git a/bundle/nvim-cmp/.github/workflows/release.yaml b/bundle/nvim-cmp/.github/workflows/release.yaml
new file mode 100644
index 000000000..1acd349ce
--- /dev/null
+++ b/bundle/nvim-cmp/.github/workflows/release.yaml
@@ -0,0 +1,19 @@
+name: "release"
+on:
+ push:
+ tags:
+ - 'v*'
+jobs:
+ luarocks-upload:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+ - name: LuaRocks Upload
+ uses: nvim-neorocks/luarocks-tag-release@v3
+ env:
+ LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}
+ with:
+ detailed_description: |
+ A completion engine plugin for neovim written in Lua.
+ Completion sources are installed from external repositories and "sourced".
+
diff --git a/bundle/nvim-cmp/Makefile b/bundle/nvim-cmp/Makefile
index 731b3f283..d108f32ef 100644
--- a/bundle/nvim-cmp/Makefile
+++ b/bundle/nvim-cmp/Makefile
@@ -1,7 +1,3 @@
-.PHONY: fmt
-fmt:
- stylua --config-path stylua.toml --glob 'lua/**/*.lua' -- lua
-
.PHONY: lint
lint:
luacheck ./lua
@@ -12,13 +8,10 @@ test:
.PHONY: pre-commit
pre-commit:
- ./utils/stylua --config-path stylua.toml --glob 'lua/**/*.lua' -- lua
luacheck lua
vusted lua
.PHONY: integration
integration:
- ./utils/stylua --config-path stylua.toml --check --glob 'lua/**/*.lua' -- lua
luacheck lua
vusted lua
-
diff --git a/bundle/nvim-cmp/README.md b/bundle/nvim-cmp/README.md
index a096cc458..48a11636c 100644
--- a/bundle/nvim-cmp/README.md
+++ b/bundle/nvim-cmp/README.md
@@ -3,34 +3,31 @@
A completion engine plugin for neovim written in Lua.
Completion sources are installed from external repositories and "sourced".
-
+https://github.com/hrsh7th/nvim-cmp/assets/22756295/afa70011-9121-4e42-aedd-0153b630eeab
Readme!
====================
-1. nvim-cmp's breaking changes are documented [here](https://github.com/hrsh7th/nvim-cmp/issues/231).
+1. There is a GitHub issue that documents [breaking changes](https://github.com/hrsh7th/nvim-cmp/issues/231) for nvim-cmp. Subscribe to the issue to be notified of upcoming breaking changes.
2. This is my hobby project. You can support me via GitHub sponsors.
-3. Bug reports are welcome, but I might not fix if you don't provide a minimal reproduction configuration and steps.
-4. The nvim-cmp documents is [here](./doc/cmp.txt).
-
-
+3. Bug reports are welcome, but don't expect a fix unless you provide minimal configuration and steps to reproduce your issue.
+4. The `cmp.mapping.preset.*` is pre-defined configuration that aims to mimic neovim's native like behavior. It can be changed without announcement. Please manage key-mapping by yourself.
Concept
====================
- Full support for LSP completion related capabilities
- Powerful customizability via Lua functions
-- Smart handling of key mapping
+- Smart handling of key mappings
- No flicker
-
Setup
====================
### Recommended Configuration
-This example configuration uses `vim-plug` as the plugin manager and `vim-vsnip` as snippet plugin.
+This example configuration uses `vim-plug` as the plugin manager and `vim-vsnip` as a snippet plugin.
```lua
call plug#begin(s:plug_dir)
@@ -59,10 +56,8 @@ Plug 'hrsh7th/vim-vsnip'
call plug#end()
-set completeopt=menu,menuone,noselect
-
lua <'] = cmp.mapping(cmp.mapping.scroll_docs(-4), { 'i', 'c' }),
- [''] = cmp.mapping(cmp.mapping.scroll_docs(4), { 'i', 'c' }),
- [''] = cmp.mapping(cmp.mapping.complete(), { 'i', 'c' }),
- [''] = cmp.config.disable, -- Specify `cmp.config.disable` if you want to remove the default `` mapping.
- [''] = cmp.mapping({
- i = cmp.mapping.abort(),
- c = cmp.mapping.close(),
- }),
- [''] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
+ window = {
+ -- completion = cmp.config.window.bordered(),
+ -- documentation = cmp.config.window.bordered(),
},
+ mapping = cmp.mapping.preset.insert({
+ [''] = cmp.mapping.scroll_docs(-4),
+ [''] = cmp.mapping.scroll_docs(4),
+ [''] = cmp.mapping.complete(),
+ [''] = cmp.mapping.abort(),
+ [''] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
+ }),
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'vsnip' }, -- For vsnip users.
@@ -100,14 +95,15 @@ lua < with each lsp server you've enabled.
require('lspconfig')[''].setup {
capabilities = capabilities
@@ -131,85 +128,12 @@ lua < lua vimrc.cmp.lsp()
- inoremap lua vimrc.cmp.snippet()
-]])
-```
-
-### Full managed completion behavior.
-
-```lua
-local cmp = require('cmp')
-
-cmp.setup {
- completion = {
- autocomplete = false, -- disable auto-completion.
- }
-}
-
-_G.vimrc = _G.vimrc or {}
-_G.vimrc.cmp = _G.vimrc.cmp or {}
-_G.vimrc.cmp.on_text_changed = function()
- local cursor = vim.api.nvim_win_get_cursor(0)
- local line = vim.api.nvim_get_current_line()
- local before = string.sub(line, 1, cursor[2] + 1)
- if before:match('%s*$') then
- cmp.complete() -- Trigger completion only if the cursor is placed at the end of line.
- end
-end
-vim.cmd([[
- augroup vimrc
- autocmd
- autocmd TextChanged,TextChangedI,TextChangedP * call luaeval('vimrc.cmp.on_text_changed()')
- augroup END
-]])
-```
-
-
-
+See the [Wiki](https://github.com/hrsh7th/nvim-cmp/wiki).
diff --git a/bundle/nvim-cmp/autoload/cmp.vim b/bundle/nvim-cmp/autoload/cmp.vim
index 43b8cc282..a331d0716 100644
--- a/bundle/nvim-cmp/autoload/cmp.vim
+++ b/bundle/nvim-cmp/autoload/cmp.vim
@@ -6,7 +6,16 @@ let s:sources = {}
"
function! cmp#register_source(name, source) abort
let l:methods = []
- for l:method in ['is_available', 'get_debug_name', 'get_trigger_characters', 'get_keyword_pattern', 'complete', 'execute', 'resolve']
+ for l:method in [
+ \ 'is_available',
+ \ 'get_debug_name',
+ \ 'get_position_encoding_kind',
+ \ 'get_trigger_characters',
+ \ 'get_keyword_pattern',
+ \ 'complete',
+ \ 'execute',
+ \ 'resolve'
+ \ ]
if has_key(a:source, l:method) && type(a:source[l:method]) == v:t_func
call add(l:methods, l:method)
endif
@@ -39,6 +48,8 @@ function! cmp#_method(bridge_id, method, args) abort
return l:source[a:method]()
elseif a:method ==# 'get_debug_name'
return l:source[a:method]()
+ elseif a:method ==# 'get_position_encoding_kind'
+ return l:source[a:method](a:args[0])
elseif a:method ==# 'get_keyword_pattern'
return l:source[a:method](a:args[0])
elseif a:method ==# 'get_trigger_characters'
diff --git a/bundle/nvim-cmp/doc/cmp.txt b/bundle/nvim-cmp/doc/cmp.txt
index bdb12d01d..935d322a8 100644
--- a/bundle/nvim-cmp/doc/cmp.txt
+++ b/bundle/nvim-cmp/doc/cmp.txt
@@ -12,45 +12,40 @@ Function |cmp-function|
Mapping |cmp-mapping|
Command |cmp-command|
Highlight |cmp-highlight|
+FileType |cmp-filetype|
Autocmd |cmp-autocmd|
Config |cmp-config|
+Config Helper |cmp-config-helper|
Develop |cmp-develop|
FAQ |cmp-faq|
-
-
==============================================================================
Abstract *cmp-abstract*
This is nvim-cmp's document.
-1. This docs uses the type definition notation like `{lsp,cmp,vim}.*`
- - You can find it `../lua/cmp/types/init.lua`.
-2. The advanced configuration is noted in wiki.
+1. This help file uses the type definition notation like `{lsp,cmp,vim}.*`
+ - You can find it in `../lua/cmp/types/init.lua`.
+2. Advanced configuration is described in the wiki.
- https://github.com/hrsh7th/nvim-cmp/wiki
-
-
==============================================================================
Concept *cmp-concept*
- Full support for LSP completion related capabilities
-- Powerful customizability via Lua functions
-- Smart handling of key mapping
+- Powerful customization abilities via Lua functions
+- Smart handling of key mappings
- No flicker
-
-
==============================================================================
Usage *cmp-usage*
-The recommendation configurations are the below.
-
-NOTE:
- 1. You must setup `snippet.expand` function.
- 2. The `cmp.setup.cmdline` won't work if you are using `native` completion menu.
- 3. You can disable the `default` options via specifying `cmp.config.disable` value.
->
+A recommended configuration can be found below.
+ NOTE:
+ 1. You must provide a `snippet.expand` function.
+ 2. `cmp.setup.cmdline` won't work if you use the `native` completion menu.
+ 3. You can disable the `default` options by specifying `cmp.config.disable` value.
+>vim
call plug#begin(s:plug_dir)
Plug 'neovim/nvim-lspconfig'
Plug 'hrsh7th/cmp-nvim-lsp'
@@ -92,18 +87,16 @@ NOTE:
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
end,
},
- mapping = {
- [''] = cmp.mapping(cmp.mapping.scroll_docs(-4), { 'i', 'c' }),
- [''] = cmp.mapping(cmp.mapping.scroll_docs(4), { 'i', 'c' }),
- [''] = cmp.mapping(cmp.mapping.complete(), { 'i', 'c' }),
- [''] = cmp.mapping({
- i = cmp.mapping.abort(),
- c = cmp.mapping.close(),
- }),
- -- Accept currently selected item. If none selected, `select` first item.
- -- Set `select` to `false` to only confirm explicitly selected items.
- [''] = cmp.mapping.confirm({ select = true }),
+ window = {
+ -- completion = cmp.config.window.bordered(),
+ -- documentation = cmp.config.window.bordered(),
},
+ mapping = cmp.mapping.preset.insert({
+ [''] = cmp.mapping.scroll_docs(-4),
+ [''] = cmp.mapping.scroll_docs(4),
+ [''] = cmp.mapping.complete(),
+ [''] = cmp.mapping.confirm({ select = true }),
+ }),
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'vsnip' }, -- For vsnip users.
@@ -117,6 +110,7 @@ NOTE:
-- `/` cmdline setup.
cmp.setup.cmdline('/', {
+ mapping = cmp.mapping.preset.cmdline(),
sources = {
{ name = 'buffer' }
}
@@ -124,6 +118,7 @@ NOTE:
-- `:` cmdline setup.
cmp.setup.cmdline(':', {
+ mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources({
{ name = 'path' }
}, {
@@ -132,65 +127,63 @@ NOTE:
})
-- Setup lspconfig.
- local capabilities = require('cmp_nvim_lsp').update_capabilities(vim.lsp.protocol.make_client_capabilities())
+ local capabilities = require('cmp_nvim_lsp').default_capabilities()
require('lspconfig')[%YOUR_LSP_SERVER%].setup {
capabilities = capabilities
}
EOF
<
-
-
==============================================================================
Function *cmp-function*
-NOTE: You can call these functions in mapping via `lua require('cmp').complete()`.
+NOTE: `lua require('cmp').complete()` can be used to call these functions in a mapping.
*cmp.setup* (config: cmp.ConfigSchema)
- Setup global configuration. See configuration option.
+ Setup global configuration. See configuration options.
*cmp.setup.filetype* (filetype: string, config: cmp.ConfigSchema)
- Setup filetype configuration to the specific filetype.
+ Setup filetype-specific configuration.
*cmp.setup.buffer* (config: cmp.ConfigSchema)
- Setup buffer configuration to the current buffer.
+ Setup configuration for the current buffer.
*cmp.setup.cmdline* (cmdtype: string, config: cmp.ConfigSchema)
- Setup cmdline configuration to the specific cmdtype.
- See |getcmdtype()|
- NOTE: nvim-cmp does not support the `=` cmdtype.
+ Setup cmdline configuration for the specific type of command.
+ See |getcmdtype()|.
+ NOTE: nvim-cmp does not support the `=` command type.
*cmp.visible* ()
- Return the completion menu is visible or not.
+ Return a boolean showing whether the completion menu is visible or not.
*cmp.get_entries* ()
- Return current all entries.
+ Return all current entries.
*cmp.get_selected_entry* ()
- Return current selected entry. (contains preselected)
+ Return currently selected entry (including preselected).
*cmp.get_active_entry* ()
- Return current selected entry. (without preselected)
+ Return currently selected entry (excluding preselected).
*cmp.close* ()
- Just close the completion menu.
+ Close the completion menu.
*cmp.abort* ()
- Closes the completion menu and restore the current line to the state when it was started current completion.
+ Closes the completion menu and restore the current line to the state before the current completion was started.
-*cmp.select_next_item* (option: { behavior = cmp.SelectBehavior })
- Select next item.
+*cmp.select_next_item* (option: { behavior = cmp.SelectBehavior, count = 1 })
+ Select the next item. Set count with large number to select pagedown.
-*cmp.select_prev_item* (option: { behavior = cmp.SelectBehavior })*
- Select prev item.
+*cmp.select_prev_item* (option: { behavior = cmp.SelectBehavior, count = 1 })
+ Select the previous item. Set count with large number to select pageup.
*cmp.scroll_docs* (delta: number)
- Scroll docs if it visible.
+ Scroll the documentation window if visible.
*cmp.complete* (option: { reason = cmp.ContextReason, config = cmp.ConfigSchema })
Invoke completion.
- The following configurations defines the key mapping to invoke only snippet completion.
->
+ The following configuration defines a key mapping to show completion only for vsnip snippets.
+>lua
cmp.setup {
mapping = {
[''] = cmp.mapping.complete({
@@ -202,14 +195,14 @@ NOTE: You can call these functions in mapping via `lua require('cmp').compl
})
}
}
-< >
+< >vim
inoremap lua require('cmp').complete({ config = { sources = { { name = 'vsnip' } } } })
<
- NOTE: The `config` means a temporary setting, but the `config.mapping` remains permanent.
+ NOTE: `config` in that case means a temporary setting, but `config.mapping` remains permanent.
*cmp.complete_common_string* ()
- Complete common string as like as shell completion behavior.
->
+ Complete common string (similar to shell completion behavior).
+>lua
cmp.setup {
mapping = {
[''] = cmp.mapping(function(fallback)
@@ -222,51 +215,81 @@ NOTE: You can call these functions in mapping via `lua require('cmp').compl
}
<
*cmp.confirm* (option: cmp.ConfirmOption, callback: function)
- Accept current selected completion item.
- If you didn't select any items and specified the `{ select = true }` for
- this, nvim-cmp will automatically select the first item.
+ Accepts the currently selected completion item.
+ If you didn't select any item and the option table contains `select = true`,
+ nvim-cmp will automatically select the first item.
-*cmp.event:on* ('%EVENT_NAME%, callback)
- Subscribe nvim-cmp's events below.
+ You can control how the completion item is injected into
+ the file through the `behavior` option:
+
+ `behavior=cmp.ConfirmBehavior.Insert`: inserts the selected item and
+ moves adjacent text to the right (default).
+ `behavior=cmp.ConfirmBehavior.Replace`: replaces adjacent text with
+ the selected item.
+>lua
+ cmp.setup {
+ mapping = {
+ [""] = cmp.mapping.confirm({ select = true, behavior = cmp.ConfirmBehavior.Replace }),
+ }
+ }
+<
+*cmp.event:on* (%EVENT_NAME%, callback)
+ Subscribe to nvim-cmp's event. Events are listed below.
- `complete_done`: emit after current completion is done.
- `confirm_done`: emit after confirmation is done.
-
-
+ - `menu_opened`: emit after opening a new completion menu. Called with a table holding a key
+ named `window`, pointing to the completion menu implementation.
+ - `menu_closed`: emit after completion menu is closed. Called with a table holding a key
+ named `window`, pointing to the completion menu implementation.
==============================================================================
Mapping *cmp-mapping*
-The nvim-cmp's mapping mechanism is complex but flexible and user-friendly.
+Nvim-cmp's mapping mechanism is complex but flexible and user-friendly.
-You can specify the mapping as function that receives the `fallback` function as arguments.
+You can specify a mapping function that receives a `fallback` function as an argument.
The `fallback` function can be used to call an existing mapping.
-For example, typical pair-wise plugins automatically defines a mapping for `` or `(`.
-The nvim-cmp will overwrite it but you can fallback to the original mapping via invoking the `fallback` function.
->
+For example, typical pair-wise plugins automatically define mappings for `` and `(`.
+Nvim-cmp will overwrite it if you provide a mapping. To call the existing mapping,
+you would need to invoke the `fallback` function.
+>lua
cmp.setup {
mapping = {
[''] = function(fallback)
if cmp.visible() then
cmp.confirm()
else
- fallback() -- If you are using vim-endwise, this fallback function will be behaive as the vim-endwise.
+ fallback() -- If you use vim-endwise, this fallback will behave the same as vim-endwise.
+ end
+ end
+ }
+ }
+< >lua
+ cmp.setup {
+ mapping = {
+ [''] = function(fallback)
+ if cmp.visible() then
+ cmp.select_next_item()
+ else
+ fallback()
end
end
}
}
<
-And you can specify the mapping modes.
->
+
+It is possible to specify the modes the mapping should be active in (`i` = insert mode, `c` = command mode, `s` = select mode):
+>lua
cmp.setup {
mapping = {
[''] = cmp.mapping(your_mapping_function, { 'i', 'c' })
}
}
<
-And you can specify the different mapping function for each modes.
->
+You can also specify different mappings for different modes by passing a table:
+>lua
cmp.setup {
mapping = {
[''] = cmp.mapping({
@@ -276,34 +299,34 @@ And you can specify the different mapping function for each modes.
}
}
<
-You can also use built-in mapping helpers.
+There are also builtin mapping helper functions you can use:
*cmp.mapping.close* ()
- Same as |cmp.close|
+ Same as |cmp.close|.
*cmp.mapping.abort* ()
- Same as |cmp.abort|
+ Same as |cmp.abort|.
- *cmp.mapping.select_next_item* (option: { behavior = cmp.SelectBehavior })
- Same as |cmp.select_next_item|
+ *cmp.mapping.select_next_item* (option: { behavior = cmp.SelectBehavior, count = 1 })
+ Same as |cmp.select_next_item|.
- *cmp.mapping.select_prev_item* (option: { behavior = cmp.SelectBehavior })
- Same as |cmp.select_prev_item|
+ *cmp.mapping.select_prev_item* (option: { behavior = cmp.SelectBehavior, count = 1 })
+ Same as |cmp.select_prev_item|.
*cmp.mapping.scroll_docs* (delta: number)
- Same as |cmp.scroll_docs|
+ Same as |cmp.scroll_docs|.
*cmp.mapping.complete* (option: cmp.CompleteParams)
- Same as |cmp.complete|
+ Same as |cmp.complete|.
*cmp.mapping.complete_common_string* ()
- Same as |cmp.complete_common_string|
+ Same as |cmp.complete_common_string|.
*cmp.mapping.confirm* (option: cmp.ConfirmOption)
- Same as |cmp.confirm|
+ Same as |cmp.confirm|.
-The built-in mapping helper is only available as a configuration option.
-If you want to call the nvim-cmp features directly, please use |cmp-function| instead.
+Built-in mapping helpers are only available as a configuration option.
+If you want to call nvim-cmp features directly, please use |cmp-function| instead.
@@ -311,9 +334,10 @@ If you want to call the nvim-cmp features directly, please use |cmp-function| in
Command *cmp-command*
*CmpStatus*
- Prints source statuses for the current buffer and states.
- Sometimes `unknown` source will be printed but it isn't problem. (e.g. `cmp-nvim-lsp`)
- That the reason is the `cmp-nvim-lsp` will registered on the InsertEnter autocmd.
+ Describes statuses and states of sources.
+ Sometimes `unknown` will be printed - this is expected.
+ For example, `cmp-nvim-lsp` registers itself on InsertEnter autocommand
+ so the status will be shown as `unknown` when running the command.
@@ -321,60 +345,98 @@ Command *cmp-command*
Highlight *cmp-highlight*
*CmpItemAbbr*
- The abbr field's highlight group.
+ Highlight group for unmatched characters of each completion field.
*CmpItemAbbrDeprecated*
- The abbr field's highlight group that only used for deprecated item.
+ Highlight group for unmatched characters of each deprecated completion field.
*CmpItemAbbrMatch*
- The matched character's highlight group.
+ Highlight group for matched characters of each completion field. Matched characters
+ must form a substring of a field which share a starting position.
*CmpItemAbbrMatchFuzzy*
- The fuzzy matched character's highlight group.
+ Highlight group for fuzzy-matched characters of each completion field.
*CmpItemKind*
- The kind field's highlight group.
+ Highlight group for the kind of the field.
+
+NOTE: `kind` is a symbol after each completion option.
*CmpItemKind%KIND_NAME%*
- The kind field's highlight group for specific `lsp.CompletionItemKind`.
- If you want to overwrite only the method kind's highlight group, you can do this.
->
+ Highlight group for the kind of the field for a specific `lsp.CompletionItemKind`.
+ If you only want to overwrite the `method` kind's highlight group, you can do this:
+>vim
highlight CmpItemKindMethod guibg=NONE guifg=Orange
<
*CmpItemMenu*
The menu field's highlight group.
+==============================================================================
+FileType *cmp-filetype*
+*cmp_menu*
+ The completion menu buffer's filetype.
+
+*cmp_docs*
+ The documentation window buffer's filetype.
==============================================================================
Autocmd *cmp-autocmd*
You can create custom autocommands for certain nvim-cmp events by defining
-autocommands for the User event with the following patterns.
+autocommands for the User event with the following patterns:
*CmpReady*
Invoked when nvim-cmp gets sourced from `plugin/cmp.lua`.
-
-
==============================================================================
Config *cmp-config*
-You can specify the following configuration option via `cmp.setup { ... }` call.
+You can use the following options via `cmp.setup { ... }` .
*cmp-config.enabled*
enabled~
`boolean | fun(): boolean`
- You can control nvim-cmp should work or not via this option.
+ Toggles the plugin on and off.
+
+ *cmp-config.performance.debounce*
+performance.debounce~
+ `number`
+ Sets debounce time
+ This is the interval used to group up completions from different sources
+ for filtering and displaying.
+
+ *cmp-config.performance.throttle*
+performance.throttle~
+ `number`
+ Sets throttle time
+ This is used to delay filtering and displaying completions.
+
+ *cmp-config.performance.fetching_timeout*
+performance.fetching_timeout~
+ `number`
+ Sets the timeout of candidate fetching process.
+ The nvim-cmp will wait to display the most prioritized source.
+
+ *cmp-config.performance.async_budget*
+performance.async_budget~
+ `number`
+ Maximum time (in ms) an async function is allowed to run during
+ one step of the event loop.
+
+ *cmp-config.performance.max_view_entries*
+performance.max_view_entries~
+ `number`
+ Maximum number of items to show in the entries list.
*cmp-config.preselect*
preselect~
`cmp.PreselectMode`
1. `cmp.PreselectMode.Item`
- nvim-cmp will pre-select the item that the source specified.
+ nvim-cmp will preselect the item that the source specified.
2. `cmp.PreselectMode.None`
- nvim-cmp wouldn't pre-select any item.
+ nvim-cmp will not preselect any items.
*cmp-config.mapping*
mapping~
@@ -384,7 +446,8 @@ mapping~
*cmp-config.snippet.expand*
snippet.expand~
`fun(option: cmp.SnippetExpansionParams)`
- The snippet expansion function. You must integrate your snippet engine plugin via this.
+ The snippet expansion function. That's how nvim-cmp interacts with a
+ particular snippet engine.
*cmp-config.completion.keyword_length*
completion.keyword_length~
@@ -399,51 +462,70 @@ completion.keyword_pattern~
*cmp-config.completion.autocomplete*
completion.autocomplete~
`cmp.TriggerEvent[] | false`
- The auto-completion trigger events. If you specify this value to false, the
- nvim-cmp does not completion automatically but you can still use the manual
- completion though.
+ The event to trigger autocompletion. If set to `false`, then completion is
+ only invoked manually (e.g. by calling `cmp.complete`).
*cmp-config.completion.completeopt*
completion.completeopt~
`string`
- The vim's completeopt like setting. See 'completeopt'.
- Besically, You don't need to modify this.
+ Like vim's completeopt setting. See 'completeopt'.
+ In general, you don't need to change this.
+
+ *cmp-config.confirmation.get_commit_characters*
+confirmation.get_commit_characters~
+ `fun(commit_characters:string[]):string[]`
+ You can append or exclude commitCharacters via this configuration option
+ function. The commitCharacters are defined by the LSP spec.
+
+ *cmp-config.formatting.expandable_indicator*
+formatting.expandable_indicator~
+ `cmp.expandable_indicator`
+ Boolean to show the `~` expandable indicator in cmp's floating window.
*cmp-config.formatting.fields*
formatting.fields~
`cmp.ItemField[]`
- The array of completion menu field to specify the order of them.
+ An array of completion fields to specify their order.
*cmp-config.formatting.format*
formatting.format~
`fun(entry: cmp.Entry, vim_item: vim.CompletedItem): vim.CompletedItem`
- The function to customize the completion menu appearance. See |complete-items|.
- This value also can be used to modify `dup` property.
- NOTE: The `vim.CompletedItem` can have special properties `abbr_hl_group`,
- `kind_hl_group` and `menu_hl_group`.
+ The function used to customize the appearance of the completion menu. See
+ |complete-items|. This value can also be used to modify the `dup` property.
+ NOTE: The `vim.CompletedItem` can contain the special properties
+ `abbr_hl_group`, `kind_hl_group` and `menu_hl_group`.
*cmp-config.matching.disallow_fuzzy_matching*
matching.disallow_fuzzy_matching~
`boolean`
- Specify disallow or allow fuzzy matching.
+ Whether to allow fuzzy matching.
+ *cmp-config.matching.disallow_fullfuzzy_matching*
+matching.disallow_fullfuzzy_matching~
+ `boolean`
+ Whether to allow full-fuzzy matching.
+
+ *cmp-config.matching.disallow_partial_fuzzy_matching*
+matching.disallow_partial_fuzzy_matching~
+ `boolean`
+ Whether to allow fuzzy matching without prefix matching.
*cmp-config.matching.disallow_partial_matching*
matching.disallow_partial_matching~
`boolean`
- Specify disallow or allow partial matching.
+ Whether to allow partial matching.
*cmp-config.matching.disallow_prefix_unmatching*
matching.disallow_prefix_unmatching~
`boolean`
- Specify disallow or allow prefix unmatching.
+ Whether to allow prefix unmatching.
*cmp-config.sorting.priority_weight*
sorting.priority_weight~
`number`
- Specifically, each item's original priority (given by its corresponding source) will be
- increased by `#sources - (source_index - 1)` multiplied by `priority_weight`.
+ Each item's original priority (given by its corresponding source) will be
+ increased by `#sources - (source_index - 1)` and multiplied by `priority_weight`.
That is, the final priority is calculated by the following formula:
->
+>lua
final_score = orig_score + ((#sources - (source_index - 1)) * sorting.priority_weight)
<
*cmp-config.sorting.comparators*
@@ -455,52 +537,47 @@ sorting.comparators~
*cmp-config.sources*
sources~
`cmp.SourceConfig[]`
- Array of the source configuration to use.
- The order will be used to the completion menu's sort order.
+ List of the sources and their configurations to use.
+ The order of the sources determines their order in the completion results.
*cmp-config.sources[n].name*
sources[n].name~
`string`
- The source name.
+ The name of the source.
*cmp-config.sources[n].option*
sources[n].option~
`table`
- The source specific custom option that defined by the source.
+ Any specific options defined by the source itself.
*cmp-config.sources[n].keyword_length*
sources[n].keyword_length~
`number`
- The source specific keyword length to trigger auto completion.
+ The source-specific keyword length to trigger auto completion.
*cmp-config.sources[n].keyword_pattern*
sources[n].keyword_pattern~
- `number`
- The source specific keyword pattern.
+ `string`
+ The source-specific keyword pattern.
*cmp-config.sources[n].trigger_characters*
sources[n].trigger_characters~
`string[]`
- The source specific keyword pattern.
+ A source-specific keyword pattern.
*cmp-config.sources[n].priority*
sources[n].priority~
`number`
- The source specific priority value.
-
- *cmp-config.sources[n].max_item_count*
-sources[n].max_item_count~
- `number`
- The source specific item count.
+ The source-specific priority value.
*cmp-config.sources[n].group_index*
sources[n].group_index~
`number`
The source group index.
- For example, You can specify the `buffer` source group index to bigger number
- if you don't want to see the buffer source items when the nvim-lsp source is available.
->
+ For instance, you can set the `buffer`'s source `group_index` to a larger number
+ if you don't want to see `buffer` source items while `nvim-lsp` source is available:
+>lua
cmp.setup {
sources = {
{ name = 'nvim_lsp', group_index = 1 },
@@ -508,8 +585,8 @@ sources[n].group_index~
}
}
<
- You can specify this via the built-in configuration helper like this.
->
+ You can also achieve this by using the built-in configuration helper like this:
+>lua
cmp.setup {
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
@@ -518,62 +595,201 @@ sources[n].group_index~
})
}
<
+
+ *cmp-config.sources[n].entry_filter*
+sources[n].entry_filter~
+ `function`
+ A source-specific entry filter, with the following function signature:
+>
+ function(entry: cmp.Entry, ctx: cmp.Context): boolean
+<
+
+ Returning `true` will keep the entry, while returning `false` will remove it.
+
+ This can be used to hide certain entries from a given source. For instance, you
+ could hide all entries with kind `Text` from the `nvim_lsp` filter using the
+ following source definition:
+>lua
+ {
+ name = 'nvim_lsp',
+ entry_filter = function(entry, ctx)
+ return require('cmp.types').lsp.CompletionItemKind[entry:get_kind()] ~= 'Text'
+ end
+ }
+<
+ Using the `ctx` parameter, you can further customize the behaviour of the
+ source.
+
*cmp-config.view*
view~
`{ entries: cmp.EntriesConfig|string }`
- Specify the view class to customize appearance.
- Currently, the possible configurations are:
+ The view class used to customize nvim-cmp's appearance.
+ Currently available configuration options are:
+
+ *cmp-config.window.{completion,documentation}.border*
+window.{completion,documentation}.border~
+ `string | string[] | nil`
+ Border characters used for the completion popup menu when |experimental.native_menu| is disabled.
+ See |nvim_open_win|.
+
+ *cmp-config.window.{completion,documentation}.winhighlight*
+window.{completion,documentation}.winhighlight~
+ `string | cmp.WinhighlightConfig`
+ Specify the window's winhighlight option.
+ See |nvim_open_win|.
+
+ *cmp-config.window.{completion,documentation}.zindex*
+window.{completion,documentation}.zindex~
+ `number`
+ The completion window's zindex.
+ See |nvim_open_win|.
+
+ *cmp-config.window.{completion,documentation}.scrolloff*
+window.completion.scrolloff~
+ `number`
+ Specify the window's scrolloff option.
+ See |'scrolloff'|.
+
+ *cmp-config.window.completion.col_offset*
+window.completion.col_offset~
+ `number`
+ Offsets the completion window relative to the cursor.
+
+ *cmp-config.window.completion.side_padding*
+window.completion.side_padding~
+ `number`
+ The ammount of padding to add on the completion window's sides
+
+ *cmp-config.window.completion.scrollbar*
+window.completion.scrollbar~
+ `boolean`
+ Whether the scrollbar should be enabled if there are more items that fit
+
+ *cmp-config.window.documentation.max_width*
+window.documentation.max_width~
+ `number`
+ The documentation window's max width.
+
+ *cmp-config.window.documentation.max_height*
+window.documentation.max_height~
+ `number`
+ The documentation window's max height.
*cmp-config.experimental.ghost_text*
experimental.ghost_text~
`boolean | { hl_group = string }`
- The boolean value to enable or disable the ghost_text feature.
+ Whether to enable the ghost_text feature.
+==============================================================================
+Config Helper *cmp-config-helper*
+You can use the following configuration helpers:
+cmp.config.compare~
+
+ TBD
+
+cmp.config.context~
+
+ The `cmp.config.context` can be used for context-aware completion toggling.
+>lua
+ cmp.setup {
+ enabled = function()
+ -- disable completion if the cursor is `Comment` syntax group.
+ return not cmp.config.context.in_syntax_group('Comment')
+ end
+ }
+<
+ *cmp.config.context.in_syntax_group* (group)
+ You can specify the vim's built-in syntax group.
+ If you use tree-sitter, you should use `cmp.config.context.in_treesitter_capture` instead.
+
+ *cmp.config.context.in_treesitter_capture* (capture)
+ You can specify the treesitter capture name.
+ If you don't use the `nvim-treesitter` plugin, this helper will not work correctly.
+
+cmp.config.mapping~
+
+ See |cmp-mapping|.
+
+cmp.config.sources~
+
+ *cmp.config.sources* (...sources)
+ You can specify multiple source arrays. The sources are grouped in the
+ order you specify, and the groups are displayed as a fallback, like chain
+ completion.
+>lua
+ cmp.setup {
+ sources = cmp.config.sources({
+ { name = 'nvim_lsp' },
+ }, {
+ { name = 'buffer' },
+ })
+ }
+<
+cmp.config.window~
+
+ *cmp.config.window.bordered* (option)
+ Make the completion window `bordered`.
+ The option is described in `cmp.ConfigSchema`.
+>lua
+ cmp.setup {
+ window = {
+ completion = cmp.config.window.bordered(),
+ documentation = cmp.config.window.bordered(),
+ }
+ }
+<
==============================================================================
Develop *cmp-develop*
-Create custom source~
+Creating a custom source~
NOTE:
- 1. The `complete` method is required. Others can be ommited.
- 2. The `callback` argument must always be called.
+ 1. The `complete` method is required. Others can be omitted.
+ 2. The `callback` function must always be called.
3. You can use only `require('cmp')` in custom source.
- 4. If the LSP spec was changed, nvim-cmp will follow it without any announcement.
- 5. You should read ./lua/cmp/types and https://microsoft.github.io/language-server-protocol/specifications/specification-current
- 6. Please add the `nvim-cmp` topic for github repo.
+ 4. If the LSP spec was changed, nvim-cmp may implement it without any announcement (potentially introducing breaking changes).
+ 5. You should read ./lua/cmp/types and https://microsoft.github.io/language-server-protocol/specifications/specification-current.
+ 6. Please add your source to the list of sources in the Wiki (https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources)
+ and if you publish it on GitHub, add the `nvim-cmp` topic so users can find it more easily.
-You can create custom source like the following example.
-
->
+Here is an example on how to create a custom source:
+>lua
local source = {}
- ---Return this source is available in current context or not. (Optional)
+ ---Return whether this source is available in the current context or not (optional).
---@return boolean
function source:is_available()
return true
end
- ---Return the debug name of this source. (Optional)
+ ---Return the debug name of this source (optional).
---@return string
function source:get_debug_name()
return 'debug name'
end
- ---Return keyword pattern for triggering completion. (Optional)
- ---If this is ommited, nvim-cmp will use default keyword pattern. See |cmp-config.completion.keyword_pattern|
+ ---Return LSP's PositionEncodingKind.
+ ---@NOTE: If this method is ommited, the default value will be `utf-16`.
+ ---@return lsp.PositionEncodingKind
+ function source:get_position_encoding_kind()
+ return 'utf-16'
+ end
+
+ ---Return the keyword pattern for triggering completion (optional).
+ ---If this is ommited, nvim-cmp will use a default keyword pattern. See |cmp-config.completion.keyword_pattern|.
---@return string
function source:get_keyword_pattern()
return [[\k\+]]
end
- ---Return trigger characters for triggering completion. (Optional)
+ ---Return trigger characters for triggering completion (optional).
function source:get_trigger_characters()
return { '.' }
end
- ---Invoke completion. (Required)
+ ---Invoke completion (required).
---@param params cmp.SourceCompletionApiParams
---@param callback fun(response: lsp.CompletionResponse|nil)
function source:complete(params, callback)
@@ -593,47 +809,70 @@ You can create custom source like the following example.
})
end
- ---Resolve completion item. (Optional)
+ ---Resolve completion item (optional). This is called right before the completion is about to be displayed.
+ ---Useful for setting the text shown in the documentation window (`completion_item.documentation`).
---@param completion_item lsp.CompletionItem
---@param callback fun(completion_item: lsp.CompletionItem|nil)
function source:resolve(completion_item, callback)
callback(completion_item)
end
- ---Execute command after item was accepted.
+ ---Executed after the item was selected.
---@param completion_item lsp.CompletionItem
---@param callback fun(completion_item: lsp.CompletionItem|nil)
function source:execute(completion_item, callback)
callback(completion_item)
end
- ---Register custom source to nvim-cmp.
- require('cmp').register_source('month', source.new())
+ ---Register your source to nvim-cmp.
+ require('cmp').register_source('month', source)
<
-
-
==============================================================================
FAQ *cmp-faq*
Why does cmp automatically select a particular item? ~
How to disable the preselect feature? ~
- The nvim-cmp respects LSP(Language Server Protocol) specification.
- The LSP spec defines the `preselect` feature for completion.
+ Nvim-cmp respects the LSP (Language Server Protocol) specification.
+ The LSP spec defines the `preselect` feature for completion.
- You can disable the `preselect` feature like the following.
->
+ You can disable the `preselect` feature like this:
+>lua
cmp.setup {
preselect = cmp.PreselectMode.None
}
<
+How to disable only specific language-server's completion?~
+
+ You can disable `completionProvider` in lspconfig configuration.
+>lua
+ lspconfig[%SERVER_NAME%].setup {
+ on_attach = function(client)
+ client.server_capabilities.completionProvider = false
+ end
+ }
+<
+
+
+How to disable commitCharacters?~
+
+ You can disable the commitCharacters feature (which is defined in LSP spec):
+>lua
+ cmp.setup {
+ confirmation = {
+ get_commit_characters = function(commit_characters)
+ return {}
+ end
+ }
+ }
+<
How to disable auto-completion?~
-How to use nvim-cmp as like omnifunc?~
+How to use nvim-cmp as omnifunc?~
- You can disable auto-completion like this.
->
+ You can disable auto-completion like this:
+>lua
cmp.setup {
...
completion = {
@@ -642,16 +881,16 @@ How to use nvim-cmp as like omnifunc?~
...
}
<
- And you can invoke completion manually.
->
+ Then you will need to invoke completion manually.
+>vim
inoremap lua require('cmp').complete()
<
-How to disable nvim-cmp on the specific buffer?~
-How to setup on the specific buffer?~
+How to disable nvim-cmp for a specific buffer?~
+How to setup nvim-cmp for a specific buffer?~
- You can setup buffer specific configuration like this.
->
+ You can setup buffer-specific configuration like this:
+>lua
cmp.setup.filetype({ 'markdown', 'help' }, {
sources = {
{ name = 'path' },
@@ -660,18 +899,35 @@ How to setup on the specific buffer?~
})
<
+How to disable the documentation window?~
+
+ Simply use the following config:
+>lua
+ cmp.setup.filetype({ 'markdown', 'help' }, {
+ window = {
+ documentation = cmp.config.disable
+ }
+ })
+<
+
+I'm using clangd. The menu items are mis-indented.~
+
+ It's caused by clangd. You can specify `--header-insertion-decorators` for
+ clangd's command-line arguments. See #999.
+
+
How to integrate with copilot.vim?~
- The copilot.vim and nvim-cmp both have a `key-mapping fallback` mechanism.
- Therefore, You should manage those plugins by yourself.
+ Copilot.vim and nvim-cmp both have a `key-mapping fallback` mechanism.
+ Therefore, you should manage those plugins by yourself.
- Fortunately, the copilot.vim has the feature that disables the fallback mechanism.
->
+ Fortunately, the copilot.vim has a feature that disables the fallback mechanism.
+>vim
let g:copilot_no_tab_map = v:true
imap (vimrc:copilot-dummy-map) copilot#Accept("\")
<
- You can manage copilot.vim's accept feature with nvim-cmp' key-mapping configuration.
->
+ You can manage copilot.vim's accept feature inside nvim-cmp's key-mapping function:
+>lua
cmp.setup {
mapping = {
[''] = cmp.mapping(function(fallback)
@@ -679,18 +935,20 @@ How to integrate with copilot.vim?~
end)
},
experimental = {
- ghost_text = false -- this feature conflict to the copilot.vim's preview.
+ ghost_text = false -- this feature conflict with copilot.vim's preview.
}
}
<
+nvim-cmp does not work as expected.~
+ There are some known issues. Please check the following.
-How to customize menu appearance?~
-
- You can see the nvim-cmp wiki (https://github.com/hrsh7th/nvim-cmp/wiki).
+ - nvim-cmp does not work with `set paste` option.
+ - Command line mode key mapping is unified regardless of `:`, `/`, `?`. Therefore, it is impossible to apply the mapping only to `:`.
+How to customize the menu appearance?~
+ Have a look at the wiki (https://github.com/hrsh7th/nvim-cmp/wiki).
==============================================================================
- vim:tw=78:ts=4:et:ft=help:norl:
-
+ vim:tw=78:ts=2:et:ft=help:norl:
diff --git a/bundle/nvim-cmp/lua/cmp/config.lua b/bundle/nvim-cmp/lua/cmp/config.lua
index 1772184b0..efb99c4e9 100644
--- a/bundle/nvim-cmp/lua/cmp/config.lua
+++ b/bundle/nvim-cmp/lua/cmp/config.lua
@@ -14,7 +14,7 @@ config.cache = cache.new()
---@type cmp.ConfigSchema
config.global = require('cmp.config.default')()
----@type table
+---@type table
config.buffers = {}
---@type table
@@ -29,14 +29,14 @@ config.onetime = {}
---Set configuration for global.
---@param c cmp.ConfigSchema
config.set_global = function(c)
- config.global = misc.merge(config.normalize(c), config.normalize(config.global))
+ config.global = config.normalize(misc.merge(c, config.global))
config.global.revision = config.global.revision or 1
config.global.revision = config.global.revision + 1
end
---Set configuration for buffer
---@param c cmp.ConfigSchema
----@param bufnr number|nil
+---@param bufnr integer
config.set_buffer = function(c, bufnr)
local revision = (config.buffers[bufnr] or {}).revision or 1
config.buffers[bufnr] = c or {}
@@ -56,11 +56,13 @@ end
---Set configuration for cmdline
---@param c cmp.ConfigSchema
----@param cmdtype string
-config.set_cmdline = function(c, cmdtype)
- local revision = (config.cmdline[cmdtype] or {}).revision or 1
- config.cmdline[cmdtype] = c or {}
- config.cmdline[cmdtype].revision = revision + 1
+---@param cmdtypes string|string[]
+config.set_cmdline = function(c, cmdtypes)
+ for _, cmdtype in ipairs(type(cmdtypes) == 'table' and cmdtypes or { cmdtypes }) do
+ local revision = (config.cmdline[cmdtype] or {}).revision or 1
+ config.cmdline[cmdtype] = c or {}
+ config.cmdline[cmdtype].revision = revision + 1
+ end
end
---Set configuration as oneshot completion.
@@ -74,7 +76,9 @@ end
---@return cmp.ConfigSchema
config.get = function()
local global_config = config.global
- if config.onetime.sources then
+
+ -- The config object already has `revision` key.
+ if #vim.tbl_keys(config.onetime) > 1 then
local onetime_config = config.onetime
return config.cache:ensure({
'get',
@@ -82,7 +86,10 @@ config.get = function()
global_config.revision or 0,
onetime_config.revision or 0,
}, function()
- return misc.merge(config.normalize(onetime_config), config.normalize(global_config))
+ local c = {}
+ c = misc.merge(c, config.normalize(onetime_config))
+ c = misc.merge(c, config.normalize(global_config))
+ return c
end)
elseif api.is_cmdline_mode() then
local cmdtype = vim.fn.getcmdtype()
@@ -94,7 +101,10 @@ config.get = function()
cmdtype,
cmdline_config.revision or 0,
}, function()
- return misc.merge(config.normalize(cmdline_config), config.normalize(global_config))
+ local c = {}
+ c = misc.merge(c, config.normalize(cmdline_config))
+ c = misc.merge(c, config.normalize(global_config))
+ return c
end)
else
local bufnr = vim.api.nvim_get_current_buf()
@@ -111,9 +121,9 @@ config.get = function()
buffer_config.revision or 0,
}, function()
local c = {}
- c = misc.merge(c, config.normalize(buffer_config))
- c = misc.merge(c, config.normalize(filetype_config))
- c = misc.merge(c, config.normalize(global_config))
+ c = misc.merge(config.normalize(c), config.normalize(buffer_config))
+ c = misc.merge(config.normalize(c), config.normalize(filetype_config))
+ c = misc.merge(config.normalize(c), config.normalize(global_config))
return c
end)
end
@@ -144,9 +154,6 @@ end
---Return the current menu is native or not.
config.is_native_menu = function()
local c = config.get()
- if c.experimental and c.experimental.native_menu then
- return true
- end
if c.view and c.view.entries then
return c.view.entries == 'native' or c.view.entries.name == 'native'
end
@@ -154,12 +161,14 @@ config.is_native_menu = function()
end
---Normalize mapping key
----@param c cmp.ConfigSchema
+---@param c any
---@return cmp.ConfigSchema
config.normalize = function(c)
-- make sure c is not 'nil'
+ ---@type any
c = c == nil and {} or c
+ -- Normalize mapping.
if c.mapping then
local normalized = {}
for k, v in pairs(c.mapping) do
@@ -168,6 +177,7 @@ config.normalize = function(c)
c.mapping = normalized
end
+ -- Notice experimental.native_menu.
if c.experimental and c.experimental.native_menu then
vim.api.nvim_echo({
{ '[nvim-cmp] ', 'Normal' },
@@ -182,6 +192,21 @@ config.normalize = function(c)
c.view.entries = c.view.entries or 'native'
end
+ -- Notice documentation.
+ if c.documentation ~= nil then
+ vim.api.nvim_echo({
+ { '[nvim-cmp] ', 'Normal' },
+ { 'documentation', 'WarningMsg' },
+ { ' is deprecated.\n', 'Normal' },
+ { '[nvim-cmp] Please use ', 'Normal' },
+ { 'window.documentation = cmp.config.window.bordered()', 'WarningMsg' },
+ { ' instead.', 'Normal' },
+ }, true, {})
+ c.window = c.window or {}
+ c.window.documentation = c.documentation
+ end
+
+ -- Notice sources.[n].opts
if c.sources then
for _, s in ipairs(c.sources) do
if s.opts and not s.option then
diff --git a/bundle/nvim-cmp/lua/cmp/config/compare.lua b/bundle/nvim-cmp/lua/cmp/config/compare.lua
index 91db9fc40..de8a12001 100644
--- a/bundle/nvim-cmp/lua/cmp/config/compare.lua
+++ b/bundle/nvim-cmp/lua/cmp/config/compare.lua
@@ -1,6 +1,5 @@
local types = require('cmp.types')
local cache = require('cmp.utils.cache')
-local misc = require('cmp.utils.misc')
local compare = {}
@@ -71,7 +70,7 @@ end
-- sortText
compare.sort_text = function(entry1, entry2)
- if misc.safe(entry1.completion_item.sortText) and misc.safe(entry2.completion_item.sortText) then
+ if entry1.completion_item.sortText and entry2.completion_item.sortText then
local diff = vim.stricmp(entry1.completion_item.sortText, entry2.completion_item.sortText)
if diff < 0 then
return true
@@ -108,7 +107,7 @@ compare.locality = setmetatable({
locality_map = {},
update = function(self)
local config = require('cmp').get_config()
- if not vim.tbl_contains(config.sorting.comparators, compare.scopes) then
+ if not vim.tbl_contains(config.sorting.comparators, compare.locality) then
return
end
@@ -132,7 +131,7 @@ compare.locality = setmetatable({
local s, e = regexp:match_str(buffer)
if s and e then
local w = string.sub(buffer, s + 1, e)
- local d = math.abs(i - cursor_row) - (is_above and 0.1 or 0)
+ local d = math.abs(i - cursor_row) - (is_above and 1 or 0)
locality_map[w] = math.min(locality_map[w] or math.huge, d)
buffer = string.sub(buffer, e + 1)
else
@@ -145,7 +144,7 @@ compare.locality = setmetatable({
self.locality_map[w] = math.min(self.locality_map[w] or d, math.abs(i - cursor_row))
end
end
- end
+ end,
}, {
__call = function(self, entry1, entry2)
local local1 = self.locality_map[entry1:get_word()]
@@ -159,7 +158,7 @@ compare.locality = setmetatable({
end
return local1 < local2
end
- end
+ end,
})
-- scopes
@@ -175,7 +174,6 @@ compare.scopes = setmetatable({
if ok then
local win, buf = vim.api.nvim_get_current_win(), vim.api.nvim_get_current_buf()
local cursor_row = vim.api.nvim_win_get_cursor(win)[1] - 1
- local ts_utils = require('nvim-treesitter.ts_utils')
-- Cursor scope.
local cursor_scope = nil
@@ -205,7 +203,8 @@ compare.scopes = setmetatable({
for _, definition in pairs(definitions) do
if s <= definition.node:start() and definition.node:end_() <= e then
if scope:id() == locals.containing_scope(definition.node, buf):id() then
- local text = ts_utils.get_node_text(definition.node)[1]
+ local get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text
+ local text = get_node_text(definition.node, buf) or ''
if not self.scopes_map[text] then
self.scopes_map[text] = depth
end
diff --git a/bundle/nvim-cmp/lua/cmp/config/context.lua b/bundle/nvim-cmp/lua/cmp/config/context.lua
index 584f38abe..c9a87174e 100644
--- a/bundle/nvim-cmp/lua/cmp/config/context.lua
+++ b/bundle/nvim-cmp/lua/cmp/config/context.lua
@@ -1,65 +1,60 @@
+local api = require('cmp.utils.api')
+
local context = {}
---Check if cursor is in syntax group
----@param group string
+---@param group string | []string
---@return boolean
context.in_syntax_group = function(group)
- local lnum, col = vim.fn.line('.'), math.min(vim.fn.col('.'), #vim.fn.getline('.'))
- for _, syn_id in ipairs(vim.fn.synstack(lnum, col)) do
+ local row, col = unpack(vim.api.nvim_win_get_cursor(0))
+ if not api.is_insert_mode() then
+ col = col + 1
+ end
+
+ for _, syn_id in ipairs(vim.fn.synstack(row, col)) do
syn_id = vim.fn.synIDtrans(syn_id) -- Resolve :highlight links
- if vim.fn.synIDattr(syn_id, 'name') == group then
+ local g = vim.fn.synIDattr(syn_id, 'name')
+ if type(group) == 'string' and g == group then
+ return true
+ elseif type(group) == 'table' and vim.tbl_contains(group, g) then
return true
end
end
+
return false
end
---Check if cursor is in treesitter capture
----@param capture string
+---@param capture string | []string
---@return boolean
context.in_treesitter_capture = function(capture)
- local highlighter = require('vim.treesitter.highlighter')
- local ts_utils = require('nvim-treesitter.ts_utils')
local buf = vim.api.nvim_get_current_buf()
-
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
row = row - 1
if vim.api.nvim_get_mode().mode == 'i' then
col = col - 1
end
- local self = highlighter.active[buf]
- if not self then
+ local get_captures_at_pos = -- See neovim/neovim#20331
+ require('vim.treesitter').get_captures_at_pos -- for neovim >= 0.8 or require('vim.treesitter').get_captures_at_position -- for neovim < 0.8
+
+ local captures_at_cursor = vim.tbl_map(function(x)
+ return x.capture
+ end, get_captures_at_pos(buf, row, col))
+
+ if vim.tbl_isempty(captures_at_cursor) then
return false
- end
-
- local node_types = {}
-
- self.tree:for_each_tree(function(tstree, tree)
- if not tstree then
- return
- end
-
- local root = tstree:root()
- local root_start_row, _, root_end_row, _ = root:range()
- if root_start_row > row or root_end_row < row then
- return
- end
-
- local query = self:get_query(tree:lang())
- if not query:query() then
- return
- end
-
- local iter = query:query():iter_captures(root, self.bufnr, row, row + 1)
- for _, node, _ in iter do
- if ts_utils.is_in_node_range(node, row, col) then
- table.insert(node_types, node:type())
+ elseif type(capture) == 'string' and vim.tbl_contains(captures_at_cursor, capture) then
+ return true
+ elseif type(capture) == 'table' then
+ for _, v in ipairs(capture) do
+ if vim.tbl_contains(captures_at_cursor, v) then
+ return true
end
end
- end, true)
+ end
- return vim.tbl_contains(node_types, capture)
+ return false
end
return context
diff --git a/bundle/nvim-cmp/lua/cmp/config/default.lua b/bundle/nvim-cmp/lua/cmp/config/default.lua
index d596444d6..02296c916 100644
--- a/bundle/nvim-cmp/lua/cmp/config/default.lua
+++ b/bundle/nvim-cmp/lua/cmp/config/default.lua
@@ -1,13 +1,12 @@
local compare = require('cmp.config.compare')
-local mapping = require('cmp.config.mapping')
-local keymap = require('cmp.utils.keymap')
local types = require('cmp.types')
local WIDE_HEIGHT = 40
---@return cmp.ConfigSchema
return function()
- return {
+ ---@type cmp.ConfigSchema
+ local config = {
enabled = function()
local disabled = false
disabled = disabled or (vim.api.nvim_buf_get_option(0, 'buftype') == 'prompt')
@@ -16,85 +15,35 @@ return function()
return not disabled
end,
- preselect = types.cmp.PreselectMode.Item,
-
- mapping = {
- [''] = mapping({
- i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }),
- c = function(fallback)
- local cmp = require('cmp')
- cmp.close()
- vim.schedule(cmp.suspend())
- fallback()
- end,
- }),
- [''] = mapping({
- i = mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Select }),
- c = function(fallback)
- local cmp = require('cmp')
- cmp.close()
- vim.schedule(cmp.suspend())
- fallback()
- end,
- }),
- [''] = mapping({
- c = function()
- local cmp = require('cmp')
- if #cmp.core:get_sources() > 0 and not require('cmp.config').is_native_menu() then
- if cmp.visible() then
- cmp.select_next_item()
- else
- cmp.complete()
- end
- else
- if vim.fn.pumvisible() == 0 then
- vim.api.nvim_feedkeys(keymap.t(''), 'in', true)
- else
- vim.api.nvim_feedkeys(keymap.t(''), 'in', true)
- end
- end
- end,
- }),
- [''] = mapping({
- c = function()
- local cmp = require('cmp')
- if #cmp.core:get_sources() > 0 and not require('cmp.config').is_native_menu() then
- if cmp.visible() then
- cmp.select_prev_item()
- else
- cmp.complete()
- end
- else
- if vim.fn.pumvisible() == 0 then
- vim.api.nvim_feedkeys(keymap.t(''), 'in', true)
- else
- vim.api.nvim_feedkeys(keymap.t(''), 'in', true)
- end
- end
- end,
- }),
- [''] = mapping(mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i', 'c' }),
- [''] = mapping(mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i', 'c' }),
- [''] = mapping.confirm({ select = false }),
- [''] = mapping.abort(),
+ performance = {
+ debounce = 60,
+ throttle = 30,
+ fetching_timeout = 500,
+ async_budget = 1,
+ max_view_entries = 200,
},
+ preselect = types.cmp.PreselectMode.Item,
+
+ mapping = {},
+
snippet = {
- expand = function()
+ expand = function(_)
error('snippet engine is not configured.')
end,
},
completion = {
- keyword_length = 1,
- keyword_pattern = [[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]],
autocomplete = {
types.cmp.TriggerEvent.TextChanged,
},
completeopt = 'menu,menuone,noselect',
+ keyword_pattern = [[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]],
+ keyword_length = 1,
},
formatting = {
+ expandable_indicator = true,
fields = { 'abbr', 'kind', 'menu' },
format = function(_, vim_item)
return vim_item
@@ -103,6 +52,8 @@ return function()
matching = {
disallow_fuzzy_matching = false,
+ disallow_fullfuzzy_matching = false,
+ disallow_partial_fuzzy_matching = true,
disallow_partial_matching = false,
disallow_prefix_unmatching = false,
},
@@ -117,7 +68,7 @@ return function()
compare.recently_used,
compare.locality,
compare.kind,
- compare.sort_text,
+ -- compare.sort_text,
compare.length,
compare.order,
},
@@ -125,13 +76,6 @@ return function()
sources = {},
- documentation = {
- border = { '', '', '', ' ', '', '', '', ' ' },
- winhighlight = 'NormalFloat:NormalFloat,FloatBorder:NormalFloat',
- maxwidth = math.floor((WIDE_HEIGHT * 2) * (vim.o.columns / (WIDE_HEIGHT * 2 * 16 / 9))),
- maxheight = math.floor(WIDE_HEIGHT * (WIDE_HEIGHT / vim.o.lines)),
- },
-
confirmation = {
default_behavior = types.cmp.ConfirmBehavior.Insert,
get_commit_characters = function(commit_characters)
@@ -146,7 +90,28 @@ return function()
},
view = {
- entries = { name = 'custom', selection_order = 'top_down' },
+ entries = {
+ name = 'custom',
+ selection_order = 'top_down',
+ },
+ },
+
+ window = {
+ completion = {
+ border = { '', '', '', '', '', '', '', '' },
+ winhighlight = 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None',
+ scrolloff = 0,
+ col_offset = 0,
+ side_padding = 1,
+ scrollbar = true,
+ },
+ documentation = {
+ max_height = math.floor(WIDE_HEIGHT * (WIDE_HEIGHT / vim.o.lines)),
+ max_width = math.floor((WIDE_HEIGHT * 2) * (vim.o.columns / (WIDE_HEIGHT * 2 * 16 / 9))),
+ border = { '', '', '', ' ', '', '', '', ' ' },
+ winhighlight = 'FloatBorder:NormalFloat',
+ },
},
}
+ return config
end
diff --git a/bundle/nvim-cmp/lua/cmp/config/mapping.lua b/bundle/nvim-cmp/lua/cmp/config/mapping.lua
index 0c2e81ae6..d5d11a518 100644
--- a/bundle/nvim-cmp/lua/cmp/config/mapping.lua
+++ b/bundle/nvim-cmp/lua/cmp/config/mapping.lua
@@ -1,5 +1,22 @@
-local mapping
-mapping = setmetatable({}, {
+local types = require('cmp.types')
+local misc = require('cmp.utils.misc')
+local keymap = require('cmp.utils.keymap')
+
+local function merge_keymaps(base, override)
+ local normalized_base = {}
+ for k, v in pairs(base) do
+ normalized_base[keymap.normalize(k)] = v
+ end
+
+ local normalized_override = {}
+ for k, v in pairs(override) do
+ normalized_override[keymap.normalize(k)] = v
+ end
+
+ return misc.merge(normalized_base, normalized_override)
+end
+
+local mapping = setmetatable({}, {
__call = function(_, invoke, modes)
if type(invoke) == 'function' then
local map = {}
@@ -12,8 +29,111 @@ mapping = setmetatable({}, {
end,
})
+---Mapping preset configuration.
+mapping.preset = {}
+
+---Mapping preset insert-mode configuration.
+mapping.preset.insert = function(override)
+ return merge_keymaps(override or {}, {
+ [''] = {
+ i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }),
+ },
+ [''] = {
+ i = mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Select }),
+ },
+ [''] = {
+ i = function()
+ local cmp = require('cmp')
+ if cmp.visible() then
+ cmp.select_next_item({ behavior = types.cmp.SelectBehavior.Insert })
+ else
+ cmp.complete()
+ end
+ end,
+ },
+ [''] = {
+ i = function()
+ local cmp = require('cmp')
+ if cmp.visible() then
+ cmp.select_prev_item({ behavior = types.cmp.SelectBehavior.Insert })
+ else
+ cmp.complete()
+ end
+ end,
+ },
+ [''] = {
+ i = mapping.confirm({ select = false }),
+ },
+ [''] = {
+ i = mapping.abort(),
+ },
+ })
+end
+
+---Mapping preset cmdline-mode configuration.
+mapping.preset.cmdline = function(override)
+ return merge_keymaps(override or {}, {
+ [''] = {
+ c = function()
+ local cmp = require('cmp')
+ if cmp.visible() then
+ cmp.select_next_item()
+ else
+ cmp.complete()
+ end
+ end,
+ },
+ [''] = {
+ c = function()
+ local cmp = require('cmp')
+ if cmp.visible() then
+ cmp.select_next_item()
+ else
+ cmp.complete()
+ end
+ end,
+ },
+ [''] = {
+ c = function()
+ local cmp = require('cmp')
+ if cmp.visible() then
+ cmp.select_prev_item()
+ else
+ cmp.complete()
+ end
+ end,
+ },
+ [''] = {
+ c = function(fallback)
+ local cmp = require('cmp')
+ if cmp.visible() then
+ cmp.select_next_item()
+ else
+ fallback()
+ end
+ end,
+ },
+ [''] = {
+ c = function(fallback)
+ local cmp = require('cmp')
+ if cmp.visible() then
+ cmp.select_prev_item()
+ else
+ fallback()
+ end
+ end,
+ },
+ [''] = {
+ c = mapping.abort(),
+ },
+ [''] = {
+ c = mapping.confirm({ select = false }),
+ },
+ })
+end
+
---Invoke completion
----@param option cmp.CompleteParams
+---@param option? cmp.CompleteParams
mapping.complete = function(option)
return function(fallback)
if not require('cmp').complete(option) then
diff --git a/bundle/nvim-cmp/lua/cmp/config/window.lua b/bundle/nvim-cmp/lua/cmp/config/window.lua
new file mode 100644
index 000000000..770ee80df
--- /dev/null
+++ b/bundle/nvim-cmp/lua/cmp/config/window.lua
@@ -0,0 +1,16 @@
+local window = {}
+
+window.bordered = function(opts)
+ opts = opts or {}
+ return {
+ border = opts.border or 'rounded',
+ winhighlight = opts.winhighlight or 'Normal:Normal,FloatBorder:Normal,CursorLine:Visual,Search:None',
+ zindex = opts.zindex or 1001,
+ scrolloff = opts.scrolloff or 0,
+ col_offset = opts.col_offset or 0,
+ side_padding = opts.side_padding or 1,
+ scrollbar = opts.scrollbar == nil and true or opts.scrollbar,
+ }
+end
+
+return window
diff --git a/bundle/nvim-cmp/lua/cmp/context.lua b/bundle/nvim-cmp/lua/cmp/context.lua
index 6188259eb..0411a5440 100644
--- a/bundle/nvim-cmp/lua/cmp/context.lua
+++ b/bundle/nvim-cmp/lua/cmp/context.lua
@@ -10,12 +10,13 @@ local api = require('cmp.utils.api')
---@field public prev_context cmp.Context
---@field public option cmp.ContextOption
---@field public filetype string
----@field public time number
----@field public bufnr number
+---@field public time integer
+---@field public bufnr integer
---@field public cursor vim.Position|lsp.Position
---@field public cursor_line string
---@field public cursor_after_line string
---@field public cursor_before_line string
+---@field public aborted boolean
local context = {}
---Create new empty context
@@ -31,8 +32,8 @@ context.empty = function()
end
---Create new context
----@param prev_context cmp.Context
----@param option cmp.ContextOption
+---@param prev_context? cmp.Context
+---@param option? cmp.ContextOption
---@return cmp.Context
context.new = function(prev_context, option)
option = option or {}
@@ -55,9 +56,14 @@ context.new = function(prev_context, option)
self.cursor.character = misc.to_utfindex(self.cursor_line, self.cursor.col)
self.cursor_before_line = string.sub(self.cursor_line, 1, self.cursor.col - 1)
self.cursor_after_line = string.sub(self.cursor_line, self.cursor.col)
+ self.aborted = false
return self
end
+context.abort = function(self)
+ self.aborted = true
+end
+
---Return context creation reason.
---@return cmp.ContextReason
context.get_reason = function(self)
@@ -65,7 +71,7 @@ context.get_reason = function(self)
end
---Get keyword pattern offset
----@return number|nil
+---@return integer
context.get_offset = function(self, keyword_pattern)
return self.cache:ensure({ 'get_offset', keyword_pattern, self.cursor_before_line }, function()
return pattern.offset(keyword_pattern .. '\\m$', self.cursor_before_line) or self.cursor.col
diff --git a/bundle/nvim-cmp/lua/cmp/core.lua b/bundle/nvim-cmp/lua/cmp/core.lua
index 169319dd9..2775cf26a 100644
--- a/bundle/nvim-cmp/lua/cmp/core.lua
+++ b/bundle/nvim-cmp/lua/cmp/core.lua
@@ -14,10 +14,6 @@ local types = require('cmp.types')
local api = require('cmp.utils.api')
local event = require('cmp.utils.event')
-local SOURCE_TIMEOUT = 500
-local DEBOUNCE_TIME = 80
-local THROTTLE_TIME = 40
-
---@class cmp.Core
---@field public suspending boolean
---@field public view cmp.View
@@ -36,9 +32,11 @@ core.new = function()
self.view.event:on('keymap', function(...)
self:on_keymap(...)
end)
- self.view.event:on('complete_done', function(evt)
- self.event:emit('complete_done', evt)
- end)
+ for _, event_name in ipairs({ 'complete_done', 'menu_opened', 'menu_closed' }) do
+ self.view.event:on(event_name, function(evt)
+ self.event:emit(event_name, evt)
+ end)
+ end
return self
end
@@ -49,17 +47,19 @@ core.register_source = function(self, s)
end
---Unregister source
----@param source_id string
+---@param source_id integer
core.unregister_source = function(self, source_id)
self.sources[source_id] = nil
end
---Get new context
----@param option cmp.ContextOption
+---@param option? cmp.ContextOption
---@return cmp.Context
core.get_context = function(self, option)
+ self.context:abort()
local prev = self.context:clone()
prev.prev_context = nil
+ prev.cache = nil
local ctx = context.new(prev, option)
self:set_context(ctx)
return self.context
@@ -74,13 +74,14 @@ end
---Suspend completion
core.suspend = function(self)
self.suspending = true
- return function()
+ -- It's needed to avoid conflicting with autocmd debouncing.
+ return vim.schedule_wrap(function()
self.suspending = false
- end
+ end)
end
---Get sources that sorted by priority
----@param filter cmp.SourceStatus[]|fun(s: cmp.Source): boolean
+---@param filter? cmp.SourceStatus[]|fun(s: cmp.Source): boolean
---@return cmp.Source[]
core.get_sources = function(self, filter)
local f = function(s)
@@ -168,7 +169,7 @@ core.on_change = function(self, trigger_event)
if vim.tbl_contains(config.get().completion.autocomplete or {}, trigger_event) then
self:complete(ctx)
else
- self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
+ self.filter.timeout = self.view:visible() and config.get().performance.throttle or 0
self:filter()
end
else
@@ -221,7 +222,7 @@ end
---Complete common string for current completed entries.
core.complete_common_string = function(self)
- if not self.view:visible() or self.view:get_active_entry() then
+ if not self.view:visible() or self.view:get_selected_entry() then
return false
end
@@ -240,7 +241,7 @@ core.complete_common_string = function(self)
config.set_onetime({})
local cursor = api.get_cursor()
- local offset = self.view:get_offset()
+ local offset = self.view:get_offset() or cursor[2]
local common_string
for _, e in ipairs(self.view:get_entries()) do
local vim_item = e:get_vim_item(offset)
@@ -250,8 +251,10 @@ core.complete_common_string = function(self)
common_string = str.get_common_string(common_string, vim_item.word)
end
end
- if common_string and #common_string > (1 + cursor[2] - offset) then
- feedkeys.call(keymap.backspace(string.sub(api.get_current_line(), offset, cursor[2])) .. common_string, 'n')
+ local cursor_before_line = api.get_cursor_before_line()
+ local pretext = cursor_before_line:sub(offset)
+ if common_string and #common_string > #pretext then
+ feedkeys.call(keymap.backspace(pretext) .. common_string, 'n')
return true
end
return false
@@ -276,17 +279,9 @@ core.complete = function(self, ctx)
if s_.incomplete and new:changed(s_.context) then
s_:complete(new, callback)
else
- for _, s__ in ipairs(self:get_sources({ source.SourceStatus.FETCHING })) do
- if s_ == s__ then
- break
- end
- if not s__.incomplete and SOURCE_TIMEOUT > s__:get_fetching_time() then
- return
- end
- end
if not self.view:get_active_entry() then
self.filter.stop()
- self.filter.timeout = self.view:visible() and DEBOUNCE_TIME or 0
+ self.filter.timeout = config.get().performance.debounce
self:filter()
end
end
@@ -296,14 +291,14 @@ core.complete = function(self, ctx)
end
if not self.view:get_active_entry() then
- self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
+ self.filter.timeout = self.view:visible() and config.get().performance.throttle or 1
self:filter()
end
end
---Update completion menu
-core.filter = async.throttle(function(self)
- self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
+local async_filter = async.wrap(function(self)
+ self.filter.timeout = config.get().performance.throttle
-- Check invalid condition.
local ignore = false
@@ -315,11 +310,13 @@ core.filter = async.throttle(function(self)
-- Check fetching sources.
local sources = {}
for _, s in ipairs(self:get_sources({ source.SourceStatus.FETCHING, source.SourceStatus.COMPLETED })) do
- if not s.incomplete and SOURCE_TIMEOUT > s:get_fetching_time() then
- -- Reserve filter call for timeout.
- self.filter.timeout = SOURCE_TIMEOUT - s:get_fetching_time()
+ -- Reserve filter call for timeout.
+ if not s.incomplete and config.get().performance.fetching_timeout > s:get_fetching_time() then
+ self.filter.timeout = config.get().performance.fetching_timeout - s:get_fetching_time()
self:filter()
- break
+ if #sources == 0 then
+ return
+ end
end
table.insert(sources, s)
end
@@ -327,20 +324,17 @@ core.filter = async.throttle(function(self)
local ctx = self:get_context()
-- Display completion results.
- self.view:open(ctx, sources)
+ local did_open = self.view:open(ctx, sources)
+ local fetching = #self:get_sources(function(s)
+ return s.status == source.SourceStatus.FETCHING
+ end)
-- Check onetime config.
- if #self:get_sources(function(s)
- if s.status == source.SourceStatus.FETCHING then
- return true
- elseif #s:get_entries(ctx) > 0 then
- return true
- end
- return false
- end) == 0 then
+ if not did_open and fetching == 0 then
config.set_onetime({})
end
-end, THROTTLE_TIME)
+end)
+core.filter = async.throttle(async_filter, config.get().performance.throttle)
---Confirm completion.
---@param e cmp.Entry
@@ -348,7 +342,10 @@ end, THROTTLE_TIME)
---@param callback function
core.confirm = function(self, e, option, callback)
if not (e and not e.confirmed) then
- return callback()
+ if callback then
+ callback()
+ end
+ return
end
e.confirmed = true
@@ -361,33 +358,39 @@ core.confirm = function(self, e, option, callback)
feedkeys.call(keymap.indentkeys(), 'n')
feedkeys.call('', 'n', function()
+ -- Emulate `` behavior to save `.` register.
local ctx = context.new()
local keys = {}
- table.insert(keys, keymap.backspace(ctx.cursor.character - misc.to_utfindex(ctx.cursor_line, e:get_offset())))
+ table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
table.insert(keys, e:get_word())
table.insert(keys, keymap.undobreak())
- feedkeys.call(table.concat(keys, ''), 'int')
+ feedkeys.call(table.concat(keys, ''), 'in')
end)
feedkeys.call('', 'n', function()
+ -- Restore the line at the time of request.
local ctx = context.new()
if api.is_cmdline_mode() then
local keys = {}
- table.insert(keys, keymap.backspace(ctx.cursor.character - misc.to_utfindex(ctx.cursor_line, e:get_offset())))
+ table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset()))
feedkeys.call(table.concat(keys, ''), 'in')
else
- vim.api.nvim_buf_set_text(0, ctx.cursor.row - 1, e:get_offset() - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, {
- string.sub(e.context.cursor_before_line, e:get_offset()),
+ vim.cmd([[silent! undojoin]])
+ -- This logic must be used nvim_buf_set_text.
+ -- If not used, the snippet engine's placeholder wil be broken.
+ vim.api.nvim_buf_set_text(0, e.context.cursor.row - 1, e:get_offset() - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, {
+ e.context.cursor_before_line:sub(e:get_offset()),
})
vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 })
end
end)
feedkeys.call('', 'n', function()
+ -- Apply additionalTextEdits.
local ctx = context.new()
- if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then
+ if #(e:get_completion_item().additionalTextEdits or {}) == 0 then
e:resolve(function()
local new = context.new()
- local text_edits = misc.safe(e:get_completion_item().additionalTextEdits) or {}
+ local text_edits = e:get_completion_item().additionalTextEdits or {}
if #text_edits == 0 then
return
end
@@ -407,18 +410,20 @@ core.confirm = function(self, e, option, callback)
if has_cursor_line_text_edit then
return
end
- vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, 'utf-16')
+ vim.cmd([[silent! undojoin]])
+ vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, e.source:get_position_encoding_kind())
end)
else
- vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, 'utf-16')
+ vim.cmd([[silent! undojoin]])
+ vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, e.source:get_position_encoding_kind())
end
end)
feedkeys.call('', 'n', function()
local ctx = context.new()
local completion_item = misc.copy(e:get_completion_item())
- if not misc.safe(completion_item.textEdit) then
+ if not completion_item.textEdit then
completion_item.textEdit = {}
- completion_item.textEdit.newText = misc.safe(completion_item.insertText) or completion_item.word or completion_item.label
+ completion_item.textEdit.newText = completion_item.insertText or completion_item.word or completion_item.label
end
local behavior = option.behavior or config.get().confirmation.default_behavior
if behavior == types.cmp.ConfirmBehavior.Replace then
@@ -427,30 +432,41 @@ core.confirm = function(self, e, option, callback)
completion_item.textEdit.range = e:get_insert_range()
end
- local diff_before = math.max(0, e.context.cursor.character - completion_item.textEdit.range.start.character)
- local diff_after = math.max(0, completion_item.textEdit.range['end'].character - e.context.cursor.character)
+ local diff_before = math.max(0, e.context.cursor.col - (completion_item.textEdit.range.start.character + 1))
+ local diff_after = math.max(0, (completion_item.textEdit.range['end'].character + 1) - e.context.cursor.col)
local new_text = completion_item.textEdit.newText
-
+ completion_item.textEdit.range.start.line = ctx.cursor.line
+ completion_item.textEdit.range.start.character = (ctx.cursor.col - 1) - diff_before
+ completion_item.textEdit.range['end'].line = ctx.cursor.line
+ completion_item.textEdit.range['end'].character = (ctx.cursor.col - 1) + diff_after
if api.is_insert_mode() then
+ if false then
+ --To use complex expansion debug.
+ vim.print({ -- luacheck: ignore
+ item = e:get_completion_item(),
+ diff_before = diff_before,
+ diff_after = diff_after,
+ new_text = new_text,
+ text_edit_new_text = completion_item.textEdit.newText,
+ range_start = completion_item.textEdit.range.start.character,
+ range_end = completion_item.textEdit.range['end'].character,
+ original_range_start = e:get_completion_item().textEdit.range.start.character,
+ original_range_end = e:get_completion_item().textEdit.range['end'].character,
+ cursor_line = ctx.cursor_line,
+ cursor_col0 = ctx.cursor.col - 1,
+ })
+ end
local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet
- completion_item.textEdit.range.start.line = ctx.cursor.line
- completion_item.textEdit.range.start.character = ctx.cursor.character - diff_before
- completion_item.textEdit.range['end'].line = ctx.cursor.line
- completion_item.textEdit.range['end'].character = ctx.cursor.character + diff_after
if is_snippet then
completion_item.textEdit.newText = ''
end
- vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-16')
+ vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-8')
+
local texts = vim.split(completion_item.textEdit.newText, '\n')
- local position = completion_item.textEdit.range.start
- position.line = position.line + (#texts - 1)
- if #texts == 1 then
- position.character = position.character + misc.to_utfindex(texts[1])
- else
- position.character = misc.to_utfindex(texts[#texts])
- end
- local pos = types.lsp.Position.to_vim(0, position)
- vim.api.nvim_win_set_cursor(0, { pos.row, pos.col - 1 })
+ vim.api.nvim_win_set_cursor(0, {
+ completion_item.textEdit.range.start.line + #texts,
+ (#texts == 1 and (completion_item.textEdit.range.start.character + #texts[1]) or #texts[#texts]),
+ })
if is_snippet then
config.get().snippet.expand({
body = new_text,
@@ -459,8 +475,8 @@ core.confirm = function(self, e, option, callback)
end
else
local keys = {}
- table.insert(keys, string.rep(keymap.t(''), diff_before))
- table.insert(keys, string.rep(keymap.t(''), diff_after))
+ table.insert(keys, keymap.backspace(ctx.cursor_line:sub(completion_item.textEdit.range.start.character + 1, ctx.cursor.col - 1)))
+ table.insert(keys, keymap.delete(ctx.cursor_line:sub(ctx.cursor.col, completion_item.textEdit.range['end'].character)))
table.insert(keys, new_text)
feedkeys.call(table.concat(keys, ''), 'in')
end
diff --git a/bundle/nvim-cmp/lua/cmp/core_spec.lua b/bundle/nvim-cmp/lua/cmp/core_spec.lua
index c37090e2b..eb0c9326d 100644
--- a/bundle/nvim-cmp/lua/cmp/core_spec.lua
+++ b/bundle/nvim-cmp/lua/cmp/core_spec.lua
@@ -8,9 +8,19 @@ local api = require('cmp.utils.api')
describe('cmp.core', function()
describe('confirm', function()
- local confirm = function(request, filter, completion_item)
+ ---@param request string
+ ---@param filter string
+ ---@param completion_item lsp.CompletionItem
+ ---@param option? { position_encoding_kind: lsp.PositionEncodingKind }
+ ---@return table
+ local confirm = function(request, filter, completion_item, option)
+ option = option or {}
+
local c = core.new()
local s = source.new('spec', {
+ get_position_encoding_kind = function()
+ return option.position_encoding_kind or types.lsp.PositionEncodingKind.UTF16
+ end,
complete = function(_, _, callback)
callback({ completion_item })
end,
@@ -23,7 +33,7 @@ describe('cmp.core', function()
end)
end)
feedkeys.call(filter, 'n', function()
- c:confirm(c.sources[s.id].entries[1], {})
+ c:confirm(c.sources[s.id].entries[1], {}, function() end)
end)
local state = {}
feedkeys.call('', 'x', function()
@@ -80,6 +90,29 @@ describe('cmp.core', function()
assert.are.same(state.cursor, { 3, 3 })
end)
+ it('#1552', function()
+ local state = confirm(keymap.t('ios.'), '', {
+ filterText = 'IsPermission',
+ insertTextFormat = 2,
+ label = 'IsPermission',
+ textEdit = {
+ newText = 'IsPermission($0)',
+ range = {
+ ['end'] = {
+ character = 3,
+ line = 0,
+ },
+ start = {
+ character = 3,
+ line = 0,
+ },
+ },
+ },
+ })
+ assert.are.same(state.buffer, { 'os.IsPermission()' })
+ assert.are.same(state.cursor, { 1, 16 })
+ end)
+
it('insertText & snippet', function()
local state = confirm('iA', 'IU', {
label = 'AIUEO',
@@ -111,6 +144,46 @@ describe('cmp.core', function()
assert.are.same(state.buffer, { '***foo', 'bar', 'baz***' })
assert.are.same(state.cursor, { 2, 2 })
end)
+
+ local char = '🗿'
+ for _, case in ipairs({
+ {
+ encoding = types.lsp.PositionEncodingKind.UTF8,
+ char_size = #char,
+ },
+ {
+ encoding = types.lsp.PositionEncodingKind.UTF16,
+ char_size = select(2, vim.str_utfindex(char)),
+ },
+ {
+ encoding = types.lsp.PositionEncodingKind.UTF32,
+ char_size = select(1, vim.str_utfindex(char)),
+ },
+ }) do
+ it('textEdit & multibyte: ' .. case.encoding, function()
+ local state = confirm(keymap.t('i%s:%s%s:%s'):format(char, char, char, char), char, {
+ label = char .. char .. char,
+ textEdit = {
+ range = {
+ start = {
+ line = 0,
+ character = case.char_size + #':',
+ },
+ ['end'] = {
+ line = 0,
+ character = case.char_size + #':' + case.char_size + case.char_size,
+ },
+ },
+ newText = char .. char .. char .. char .. char,
+ },
+ }, {
+ position_encoding_kind = case.encoding,
+ })
+ vim.print({ state = state, case = case })
+ assert.are.same(state.buffer, { ('%s:%s%s%s%s%s:%s'):format(char, char, char, char, char, char, char) })
+ assert.are.same(state.cursor, { 1, #('%s:%s%s%s%s%s'):format(char, char, char, char, char, char) })
+ end)
+ end
end)
describe('cmdline-mode', function()
diff --git a/bundle/nvim-cmp/lua/cmp/entry.lua b/bundle/nvim-cmp/lua/cmp/entry.lua
index 83c53a209..50b6535ad 100644
--- a/bundle/nvim-cmp/lua/cmp/entry.lua
+++ b/bundle/nvim-cmp/lua/cmp/entry.lua
@@ -7,15 +7,15 @@ local types = require('cmp.types')
local matcher = require('cmp.matcher')
---@class cmp.Entry
----@field public id number
+---@field public id integer
---@field public cache cmp.Cache
---@field public match_cache cmp.Cache
----@field public score number
+---@field public score integer
---@field public exact boolean
---@field public matches table
---@field public context cmp.Context
---@field public source cmp.Source
----@field public source_offset number
+---@field public source_offset integer
---@field public source_insert_range lsp.Range
---@field public source_replace_range lsp.Range
---@field public completion_item lsp.CompletionItem
@@ -29,8 +29,9 @@ local entry = {}
---@param ctx cmp.Context
---@param source cmp.Source
---@param completion_item lsp.CompletionItem
+---@param item_defaults? lsp.internal.CompletionItemDefaults
---@return cmp.Entry
-entry.new = function(ctx, source, completion_item)
+entry.new = function(ctx, source, completion_item, item_defaults)
local self = setmetatable({}, { __index = entry })
self.id = misc.id('entry.new')
self.cache = cache.new()
@@ -43,7 +44,7 @@ entry.new = function(ctx, source, completion_item)
self.source_offset = source.request_offset
self.source_insert_range = source:get_default_insert_range()
self.source_replace_range = source:get_default_replace_range()
- self.completion_item = completion_item
+ self.completion_item = self:fill_defaults(completion_item, item_defaults)
self.resolved_completion_item = nil
self.resolved_callbacks = {}
self.resolving = false
@@ -52,20 +53,23 @@ entry.new = function(ctx, source, completion_item)
end
---Make offset value
----@return number
+---@return integer
entry.get_offset = function(self)
- return self.cache:ensure({ 'get_offset', self.resolved_completion_item and 1 or 0 }, function()
+ return self.cache:ensure('get_offset', function()
local offset = self.source_offset
- if misc.safe(self:get_completion_item().textEdit) then
- local range = misc.safe(self:get_completion_item().textEdit.insert) or misc.safe(self:get_completion_item().textEdit.range)
+ if self:get_completion_item().textEdit then
+ local range = self:get_insert_range()
if range then
- local c = misc.to_vimindex(self.context.cursor_line, range.start.character)
- for idx = c, self.source_offset do
- if not char.is_white(string.byte(self.context.cursor_line, idx)) then
- offset = idx
- break
+ offset = self.context.cache:ensure('entry:' .. 'get_offset:' .. tostring(range.start.character), function()
+ local start = math.min(range.start.character + 1, offset)
+ for idx = start, self.source_offset do
+ local byte = string.byte(self.context.cursor_line, idx)
+ if byte == nil or not char.is_white(byte) then
+ return idx
+ end
end
- end
+ return offset
+ end)
end
else
-- NOTE
@@ -101,14 +105,14 @@ end
---NOTE: This method doesn't clear the cache after completionItem/resolve.
---@return string
entry.get_word = function(self)
- return self.cache:ensure({ 'get_word' }, function()
+ return self.cache:ensure('get_word', function()
--NOTE: This is nvim-cmp specific implementation.
- if misc.safe(self:get_completion_item().word) then
+ if self:get_completion_item().word then
return self:get_completion_item().word
end
local word
- if misc.safe(self:get_completion_item().textEdit) and not misc.empty(self:get_completion_item().textEdit.newText) then
+ if self:get_completion_item().textEdit and not misc.empty(self:get_completion_item().textEdit.newText) then
word = str.trim(self:get_completion_item().textEdit.newText)
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
word = vim.lsp.util.parse_snippet(word)
@@ -126,20 +130,24 @@ entry.get_word = function(self)
word = str.trim(self:get_completion_item().label)
end
return str.oneline(word)
- end)
+ end) --[[@as string]]
end
---Get overwrite information
----@return number, number
+---@return integer[]
entry.get_overwrite = function(self)
- return self.cache:ensure({ 'get_overwrite', self.resolved_completion_item and 1 or 0 }, function()
- if misc.safe(self:get_completion_item().textEdit) then
- local r = misc.safe(self:get_completion_item().textEdit.insert) or misc.safe(self:get_completion_item().textEdit.range)
- local s = misc.to_vimindex(self.context.cursor_line, r.start.character)
- local e = misc.to_vimindex(self.context.cursor_line, r['end'].character)
- local before = self.context.cursor.col - s
- local after = e - self.context.cursor.col
- return { before, after }
+ return self.cache:ensure('get_overwrite', function()
+ if self:get_completion_item().textEdit then
+ local range = self:get_insert_range()
+ if range then
+ return self.context.cache:ensure('entry:' .. 'get_overwrite:' .. tostring(range.start.character) .. ':' .. tostring(range['end'].character), function()
+ local vim_start = range.start.character + 1
+ local vim_end = range['end'].character + 1
+ local before = self.context.cursor.col - vim_start
+ local after = vim_end - self.context.cursor.col
+ return { before, after }
+ end)
+ end
end
return { 0, 0 }
end)
@@ -148,9 +156,9 @@ end
---Create filter text
---@return string
entry.get_filter_text = function(self)
- return self.cache:ensure({ 'get_filter_text', self.resolved_completion_item and 1 or 0 }, function()
+ return self.cache:ensure('get_filter_text', function()
local word
- if misc.safe(self:get_completion_item().filterText) then
+ if self:get_completion_item().filterText then
word = self:get_completion_item().filterText
else
word = str.trim(self:get_completion_item().label)
@@ -162,14 +170,14 @@ end
---Get LSP's insert text
---@return string
entry.get_insert_text = function(self)
- return self.cache:ensure({ 'get_insert_text', self.resolved_completion_item and 1 or 0 }, function()
+ return self.cache:ensure('get_insert_text', function()
local word
- if misc.safe(self:get_completion_item().textEdit) then
+ if self:get_completion_item().textEdit then
word = str.trim(self:get_completion_item().textEdit.newText)
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
end
- elseif misc.safe(self:get_completion_item().insertText) then
+ elseif self:get_completion_item().insertText then
word = str.trim(self:get_completion_item().insertText)
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
@@ -188,12 +196,12 @@ entry.is_deprecated = function(self)
end
---Return view information.
----@param suggest_offset number
----@param entries_buf number The buffer this entry will be rendered into.
----@return { abbr: { text: string, bytes: number, width: number, hl_group: string }, kind: { text: string, bytes: number, width: number, hl_group: string }, menu: { text: string, bytes: number, width: number, hl_group: string } }
+---@param suggest_offset integer
+---@param entries_buf integer The buffer this entry will be rendered into.
+---@return { abbr: { text: string, bytes: integer, width: integer, hl_group: string }, kind: { text: string, bytes: integer, width: integer, hl_group: string }, menu: { text: string, bytes: integer, width: integer, hl_group: string } }
entry.get_view = function(self, suggest_offset, entries_buf)
local item = self:get_vim_item(suggest_offset)
- return self.cache:ensure({ 'get_view', self.resolved_completion_item and 1 or 0, entries_buf }, function()
+ return self.cache:ensure('get_view:' .. tostring(entries_buf), function()
local view = {}
-- The result of vim.fn.strdisplaywidth depends on which buffer it was
-- called in because it reads the values of the option 'tabstop' when
@@ -221,24 +229,25 @@ entry.get_view = function(self, suggest_offset, entries_buf)
end
---Make vim.CompletedItem
----@param suggest_offset number
+---@param suggest_offset integer
---@return vim.CompletedItem
entry.get_vim_item = function(self, suggest_offset)
- return self.cache:ensure({ 'get_vim_item', suggest_offset, self.resolved_completion_item and 1 or 0 }, function()
+ return self.cache:ensure('get_vim_item:' .. tostring(suggest_offset), function()
local completion_item = self:get_completion_item()
local word = self:get_word()
local abbr = str.oneline(completion_item.label)
-- ~ indicator
- local is_snippet = false
- if #(misc.safe(completion_item.additionalTextEdits) or {}) > 0 then
- is_snippet = true
+ local is_expandable = false
+ local expandable_indicator = config.get().formatting.expandable_indicator
+ if #(completion_item.additionalTextEdits or {}) > 0 then
+ is_expandable = true
elseif completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
- is_snippet = self:get_insert_text() ~= word
+ is_expandable = self:get_insert_text() ~= word
elseif completion_item.kind == types.lsp.CompletionItemKind.Snippet then
- is_snippet = true
+ is_expandable = true
end
- if is_snippet then
+ if expandable_indicator and is_expandable then
abbr = abbr .. '~'
end
@@ -249,19 +258,19 @@ entry.get_vim_item = function(self, suggest_offset)
-- labelDetails.
local menu = nil
- if misc.safe(completion_item.labelDetails) then
+ if completion_item.labelDetails then
menu = ''
- if misc.safe(completion_item.labelDetails.detail) then
+ if completion_item.labelDetails.detail then
menu = menu .. completion_item.labelDetails.detail
end
- if misc.safe(completion_item.labelDetails.description) then
+ if completion_item.labelDetails.description then
menu = menu .. completion_item.labelDetails.description
end
end
-- remove duplicated string.
if self:get_offset() ~= self.context.cursor.col then
- for i = 1, #word - 1 do
+ for i = 1, #word do
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
word = string.sub(word, 1, i - 1)
break
@@ -269,10 +278,13 @@ entry.get_vim_item = function(self, suggest_offset)
end
end
+ local cmp_opts = self:get_completion_item().cmp or {}
+
local vim_item = {
word = word,
abbr = abbr,
- kind = types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
+ kind = cmp_opts.kind_text or types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
+ kind_hl_group = cmp_opts.kind_hl_group,
menu = menu,
dup = self:get_completion_item().dup or 1,
}
@@ -293,24 +305,25 @@ end
---Get commit characters
---@return string[]
entry.get_commit_characters = function(self)
- return misc.safe(self:get_completion_item().commitCharacters) or {}
+ return self:get_completion_item().commitCharacters or {}
end
---Return insert range
---@return lsp.Range|nil
entry.get_insert_range = function(self)
local insert_range
- if misc.safe(self:get_completion_item().textEdit) then
- if misc.safe(self:get_completion_item().textEdit.insert) then
+ if self:get_completion_item().textEdit then
+ if self:get_completion_item().textEdit.insert then
insert_range = self:get_completion_item().textEdit.insert
else
- insert_range = self:get_completion_item().textEdit.range
+ insert_range = self:get_completion_item().textEdit.range --[[@as lsp.Range]]
end
+ insert_range = self:convert_range_encoding(insert_range)
else
insert_range = {
start = {
line = self.context.cursor.row - 1,
- character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), self.source_insert_range.start.character),
+ character = self:get_offset() - 1,
},
['end'] = self.source_insert_range['end'],
}
@@ -321,15 +334,22 @@ end
---Return replace range
---@return lsp.Range|nil
entry.get_replace_range = function(self)
- return self.cache:ensure({ 'get_replace_range', self.resolved_completion_item and 1 or 0 }, function()
+ return self.cache:ensure('get_replace_range', function()
local replace_range
- if misc.safe(self:get_completion_item().textEdit) and misc.safe(self:get_completion_item().textEdit.replace) then
- replace_range = self:get_completion_item().textEdit.replace
- else
+ if self:get_completion_item().textEdit then
+ if self:get_completion_item().textEdit.replace then
+ replace_range = self:get_completion_item().textEdit.replace
+ else
+ replace_range = self:get_completion_item().textEdit.range --[[@as lsp.Range]]
+ end
+ replace_range = self:convert_range_encoding(replace_range)
+ end
+
+ if not replace_range or ((self.context.cursor.col - 1) == replace_range['end'].character) then
replace_range = {
start = {
line = self.source_replace_range.start.line,
- character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), self.source_replace_range.start.character),
+ character = self:get_offset() - 1,
},
['end'] = self.source_replace_range['end'],
}
@@ -341,17 +361,12 @@ end
---Match line.
---@param input string
---@param matching_config cmp.MatchingConfig
----@return { score: number, matches: table[] }
+---@return { score: integer, matches: table[] }
entry.match = function(self, input, matching_config)
- return self.match_cache:ensure({
- input,
- self.resolved_completion_item and 1 or 0,
- matching_config.disallow_fuzzy_matching and 1 or 0,
- matching_config.disallow_partial_matching and 1 or 0,
- matching_config.disallow_prefix_unmatching and 1 or 0,
- }, function()
+ return self.match_cache:ensure(input .. ':' .. (self.resolved_completion_item and '1' or '0' .. ':') .. (matching_config.disallow_fuzzy_matching and '1' or '0') .. ':' .. (matching_config.disallow_partial_fuzzy_matching and '1' or '0') .. ':' .. (matching_config.disallow_partial_matching and '1' or '0') .. ':' .. (matching_config.disallow_prefix_unmatching and '1' or '0'), function()
local option = {
disallow_fuzzy_matching = matching_config.disallow_fuzzy_matching,
+ disallow_partial_fuzzy_matching = matching_config.disallow_partial_fuzzy_matching,
disallow_partial_matching = matching_config.disallow_partial_matching,
disallow_prefix_unmatching = matching_config.disallow_prefix_unmatching,
synonyms = {
@@ -360,27 +375,42 @@ entry.match = function(self, input, matching_config)
},
}
- local score, matches, _
- score, matches = matcher.match(input, self:get_filter_text(), option)
+ local score, matches, filter_text, _
+ local checked = {} ---@type table
+
+ filter_text = self:get_filter_text()
+ checked[filter_text] = true
+ score, matches = matcher.match(input, filter_text, option)
-- Support the language server that doesn't respect VSCode's behaviors.
+ local prefix = ''
if score == 0 then
- if misc.safe(self:get_completion_item().textEdit) and not misc.empty(self:get_completion_item().textEdit.newText) then
+ if self:get_completion_item().textEdit and not misc.empty(self:get_completion_item().textEdit.newText) then
local diff = self.source_offset - self:get_offset()
if diff > 0 then
- local prefix = string.sub(self.context.cursor_line, self:get_offset(), self:get_offset() + diff)
- local accept = false
+ prefix = string.sub(self.context.cursor_line, self:get_offset(), self:get_offset() + diff)
+ local accept = nil
accept = accept or string.match(prefix, '^[^%a]+$')
accept = accept or string.find(self:get_completion_item().textEdit.newText, prefix, 1, true)
if accept then
- score, matches = matcher.match(input, prefix .. self:get_filter_text(), option)
+ filter_text = prefix .. self:get_filter_text()
+ if not checked[filter_text] then
+ checked[filter_text] = true
+ score, matches = matcher.match(input, filter_text, option)
+ end
end
end
end
end
- if self:get_filter_text() ~= self:get_completion_item().label then
- _, matches = matcher.match(input, self:get_completion_item().label, { self:get_word() })
+ -- Fix highlight if filterText is not the same to vim_item.abbr.
+ if score > 0 then
+ local vim_item = self:get_vim_item(self.source_offset)
+ filter_text = vim_item.abbr or vim_item.word
+ if not checked[filter_text] then
+ local diff = self.source_offset - self:get_offset()
+ _, matches = matcher.match(input:sub(1 + diff), filter_text, option)
+ end
end
return { score = score, matches = matches }
@@ -390,7 +420,7 @@ end
---Get resolved completion item if possible.
---@return lsp.CompletionItem
entry.get_completion_item = function(self)
- return self.cache:ensure({ 'get_completion_item', self.resolved_completion_item and 1 or 0 }, function()
+ return self.cache:ensure('get_completion_item', function()
if self.resolved_completion_item then
local completion_item = misc.copy(self.completion_item)
for k, v in pairs(self.resolved_completion_item) do
@@ -410,7 +440,7 @@ entry.get_documentation = function(self)
local documents = {}
-- detail
- if misc.safe(item.detail) and item.detail ~= '' then
+ if item.detail and item.detail ~= '' then
local ft = self.context.filetype
local dot_index = string.find(ft, '%.')
if dot_index ~= nil then
@@ -422,13 +452,23 @@ entry.get_documentation = function(self)
})
end
- if type(item.documentation) == 'string' and item.documentation ~= '' then
- table.insert(documents, {
- kind = types.lsp.MarkupKind.PlainText,
- value = str.trim(item.documentation),
- })
- elseif type(item.documentation) == 'table' and item.documentation.value ~= '' then
- table.insert(documents, item.documentation)
+ local documentation = item.documentation
+ if type(documentation) == 'string' and documentation ~= '' then
+ local value = str.trim(documentation)
+ if value ~= '' then
+ table.insert(documents, {
+ kind = types.lsp.MarkupKind.PlainText,
+ value = value,
+ })
+ end
+ elseif type(documentation) == 'table' and not misc.empty(documentation.value) then
+ local value = str.trim(documentation.value)
+ if value ~= '' then
+ table.insert(documents, {
+ kind = documentation.kind,
+ value = value,
+ })
+ end
end
return vim.lsp.util.convert_input_to_markdown_lines(documents)
@@ -437,7 +477,7 @@ end
---Get completion item kind
---@return lsp.CompletionItemKind
entry.get_kind = function(self)
- return misc.safe(self:get_completion_item().kind) or types.lsp.CompletionItemKind.Text
+ return self:get_completion_item().kind or types.lsp.CompletionItemKind.Text
end
---Execute completion item's command.
@@ -457,7 +497,12 @@ entry.resolve = function(self, callback)
if not self.resolving then
self.resolving = true
self.source:resolve(self.completion_item, function(completion_item)
- self.resolved_completion_item = misc.safe(completion_item) or self.completion_item
+ self.resolving = false
+ if not completion_item then
+ return
+ end
+ self.resolved_completion_item = completion_item or self.completion_item
+ self.cache:clear()
for _, c in ipairs(self.resolved_callbacks) do
c()
end
@@ -465,4 +510,57 @@ entry.resolve = function(self, callback)
end
end
+---@param completion_item lsp.CompletionItem
+---@param defaults? lsp.internal.CompletionItemDefaults
+---@return lsp.CompletionItem
+entry.fill_defaults = function(_, completion_item, defaults)
+ defaults = defaults or {}
+
+ if defaults.data then
+ completion_item.data = completion_item.data or defaults.data
+ end
+
+ if defaults.commitCharacters then
+ completion_item.commitCharacters = completion_item.commitCharacters or defaults.commitCharacters
+ end
+
+ if defaults.insertTextFormat then
+ completion_item.insertTextFormat = completion_item.insertTextFormat or defaults.insertTextFormat
+ end
+
+ if defaults.insertTextMode then
+ completion_item.insertTextMode = completion_item.insertTextMode or defaults.insertTextMode
+ end
+
+ if defaults.editRange then
+ if not completion_item.textEdit then
+ if defaults.editRange.insert then
+ completion_item.textEdit = {
+ insert = defaults.editRange.insert,
+ replace = defaults.editRange.replace,
+ newText = completion_item.textEditText or completion_item.label,
+ }
+ else
+ completion_item.textEdit = {
+ range = defaults.editRange, --[[@as lsp.Range]]
+ newText = completion_item.textEditText or completion_item.label,
+ }
+ end
+ end
+ end
+
+ return completion_item
+end
+
+---Convert the oneline range encoding.
+entry.convert_range_encoding = function(self, range)
+ local from_encoding = self.source:get_position_encoding_kind()
+ return self.context.cache:ensure('entry.convert_range_encoding:' .. range.start.character .. ':' .. range['end'].character .. ':' .. from_encoding, function()
+ return {
+ start = types.lsp.Position.to_utf8(self.context.cursor_line, range.start, from_encoding),
+ ['end'] = types.lsp.Position.to_utf8(self.context.cursor_line, range['end'], from_encoding),
+ }
+ end)
+end
+
return entry
diff --git a/bundle/nvim-cmp/lua/cmp/entry_spec.lua b/bundle/nvim-cmp/lua/cmp/entry_spec.lua
index d01125cac..3cb9b5861 100644
--- a/bundle/nvim-cmp/lua/cmp/entry_spec.lua
+++ b/bundle/nvim-cmp/lua/cmp/entry_spec.lua
@@ -1,6 +1,4 @@
local spec = require('cmp.utils.spec')
-local source = require('cmp.source')
-local async = require('cmp.utils.async')
local entry = require('cmp.entry')
@@ -290,41 +288,6 @@ describe('entry', function()
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'string')
end)
- it('[ansiblels] 1', function()
- local item = {
- detail = 'ansible.builtin',
- filterText = 'blockinfile ansible.builtin.blockinfile',
- kind = 7,
- label = 'blockinfile',
- sortText = '2_blockinfile',
- textEdit = {
- newText = '',
- range = {
- ['end'] = {
- character = 7,
- line = 15,
- },
- start = {
- character = 6,
- line = 15,
- },
- },
- },
- }
- local s = source.new('dummy', {
- resolve = function(_, _, callback)
- item.textEdit.newText = 'modified'
- callback(item)
- end,
- })
- local e = entry.new(spec.state('', 1, 1).manual(), s, item)
- assert.are.equal(e:get_vim_item(e:get_offset()).word, 'blockinfile')
- async.sync(function(done)
- e:resolve(done)
- end, 100)
- assert.are.equal(e:get_vim_item(e:get_offset()).word, 'blockinfile')
- end)
-
it('[#47] word should not contain \\n character', function()
local state = spec.state('', 1, 1)
@@ -339,4 +302,65 @@ describe('entry', function()
assert.are.equal(e:get_vim_item(e:get_offset()).word, '__init__(self) -> None:')
assert.are.equal(e:get_filter_text(), '__init__')
end)
+
+ -- I can't understand this test case...
+ -- it('[#1533] keyword pattern that include whitespace', function()
+ -- local state = spec.state(' ', 1, 2)
+ -- local state_source = state.source()
+
+ -- state_source.get_keyword_pattern = function(_)
+ -- return '.'
+ -- end
+
+ -- state.input(' ')
+ -- local e = entry.new(state.manual(), state_source, {
+ -- filterText = "constructor() {\n ... st = 'test';\n ",
+ -- kind = 1,
+ -- label = "constructor() {\n ... st = 'test';\n }",
+ -- textEdit = {
+ -- newText = "constructor() {\n this.test = 'test';\n }",
+ -- range = {
+ -- ['end'] = {
+ -- character = 2,
+ -- line = 2,
+ -- },
+ -- start = {
+ -- character = 0,
+ -- line = 2,
+ -- },
+ -- },
+ -- },
+ -- })
+ -- assert.are.equal(e:get_offset(), 2)
+ -- assert.are.equal(e:get_vim_item(e:get_offset()).word, 'constructor() {')
+ -- end)
+
+ it('[#1533] clang regression test', function()
+ local state = spec.state('jsonReader', 3, 11)
+ local state_source = state.source()
+
+ state.input('.')
+ local e = entry.new(state.manual(), state_source, {
+ filterText = 'getPath()',
+ kind = 1,
+ label = 'getPath()',
+ textEdit = {
+ newText = 'getPath()',
+ range = {
+ ['end'] = {
+ character = 11,
+ col = 12,
+ line = 2,
+ row = 3,
+ },
+ start = {
+ character = 11,
+ line = 2,
+ },
+ },
+ },
+ })
+ assert.are.equal(e:get_offset(), 12)
+ assert.are.equal(e:get_vim_item(e:get_offset()).word, 'getPath()')
+ end)
end)
diff --git a/bundle/nvim-cmp/lua/cmp/init.lua b/bundle/nvim-cmp/lua/cmp/init.lua
index dbcd8668b..03722b65e 100644
--- a/bundle/nvim-cmp/lua/cmp/init.lua
+++ b/bundle/nvim-cmp/lua/cmp/init.lua
@@ -5,6 +5,7 @@ local feedkeys = require('cmp.utils.feedkeys')
local autocmd = require('cmp.utils.autocmd')
local keymap = require('cmp.utils.keymap')
local misc = require('cmp.utils.misc')
+local async = require('cmp.utils.async')
local cmp = {}
@@ -29,6 +30,7 @@ cmp.config.disable = misc.none
cmp.config.compare = require('cmp.config.compare')
cmp.config.sources = require('cmp.config.sources')
cmp.config.mapping = require('cmp.config.mapping')
+cmp.config.window = require('cmp.config.window')
---Sync asynchronous process.
cmp.sync = function(callback)
@@ -48,7 +50,7 @@ end
---Register completion sources
---@param name string
---@param s cmp.Source
----@return number
+---@return integer
cmp.register_source = function(name, s)
local src = source.new(name, s)
cmp.core:register_source(src)
@@ -56,7 +58,7 @@ cmp.register_source = function(name, s)
end
---Unregister completion source
----@param id number
+---@param id integer
cmp.unregister_source = function(id)
cmp.core:unregister_source(id)
end
@@ -106,7 +108,6 @@ cmp.close = cmp.sync(function()
if cmp.core.view:visible() then
local release = cmp.core:suspend()
cmp.core.view:close()
- cmp.core:reset()
vim.schedule(release)
return true
else
@@ -129,6 +130,8 @@ end)
---Select next item if possible
cmp.select_next_item = cmp.sync(function(option)
option = option or {}
+ option.behavior = option.behavior or cmp.SelectBehavior.Insert
+ option.count = option.count or 1
if cmp.core.view:visible() then
local release = cmp.core:suspend()
@@ -136,11 +139,10 @@ cmp.select_next_item = cmp.sync(function(option)
vim.schedule(release)
return true
elseif vim.fn.pumvisible() == 1 then
- -- Special handling for native pum. Required to facilitate key mapping processing.
- if (option.behavior or cmp.SelectBehavior.Insert) == cmp.SelectBehavior.Insert then
- feedkeys.call(keymap.t(''), 'in')
+ if option.behavior == cmp.SelectBehavior.Insert then
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'in')
else
- feedkeys.call(keymap.t(''), 'in')
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'in')
end
return true
end
@@ -150,6 +152,8 @@ end)
---Select prev item if possible
cmp.select_prev_item = cmp.sync(function(option)
option = option or {}
+ option.behavior = option.behavior or cmp.SelectBehavior.Insert
+ option.count = option.count or 1
if cmp.core.view:visible() then
local release = cmp.core:suspend()
@@ -157,11 +161,10 @@ cmp.select_prev_item = cmp.sync(function(option)
vim.schedule(release)
return true
elseif vim.fn.pumvisible() == 1 then
- -- Special handling for native pum. Required to facilitate key mapping processing.
- if (option.behavior or cmp.SelectBehavior.Insert) == cmp.SelectBehavior.Insert then
- feedkeys.call(keymap.t(''), 'in')
+ if option.behavior == cmp.SelectBehavior.Insert then
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'in')
else
- feedkeys.call(keymap.t(''), 'in')
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'in')
end
return true
end
@@ -170,7 +173,7 @@ end)
---Scrolling documentation window if possible
cmp.scroll_docs = cmp.sync(function(delta)
- if cmp.core.view:visible() then
+ if cmp.core.view.docs_view:visible() then
cmp.core.view:scroll_docs(delta)
return true
else
@@ -181,25 +184,35 @@ end)
---Confirm completion
cmp.confirm = cmp.sync(function(option, callback)
option = option or {}
+ option.select = option.select or false
+ option.behavior = option.behavior or cmp.get_config().confirmation.default_behavior or cmp.ConfirmBehavior.Insert
callback = callback or function() end
- local e = cmp.core.view:get_selected_entry() or (option.select and cmp.core.view:get_first_entry() or nil)
- if e then
- cmp.core:confirm(e, {
- behavior = option.behavior,
- }, function()
- callback()
- cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.TriggerOnly }))
- end)
- return true
- else
- -- Special handling for native puma. Required to facilitate key mapping processing.
- if vim.fn.complete_info({ 'selected' }).selected ~= -1 then
- feedkeys.call(keymap.t(''), 'in')
+ if cmp.core.view:visible() then
+ local e = cmp.core.view:get_selected_entry()
+ if not e and option.select then
+ e = cmp.core.view:get_first_entry()
+ end
+ if e then
+ cmp.core:confirm(e, {
+ behavior = option.behavior,
+ }, function()
+ callback()
+ cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.TriggerOnly }))
+ end)
+ return true
+ end
+ elseif vim.fn.pumvisible() == 1 then
+ local index = vim.fn.complete_info({ 'selected' }).selected
+ if index == -1 and option.select then
+ index = 0
+ end
+ if index ~= -1 then
+ vim.api.nvim_select_popupmenu_item(index, true, true, {})
return true
end
- return false
end
+ return false
end)
---Show status
@@ -282,39 +295,28 @@ cmp.setup = setmetatable({
end,
})
-autocmd.subscribe('InsertEnter', function()
- feedkeys.call('', 'i', function()
- if config.enabled() then
- cmp.core:prepare()
- cmp.core:on_change('InsertEnter')
- end
- end)
-end)
-
-autocmd.subscribe('InsertLeave', function()
- cmp.core:reset()
- cmp.core.view:close()
-end)
-
-autocmd.subscribe('CmdlineEnter', function()
+-- In InsertEnter autocmd, vim will detects mode=normal unexpectedly.
+local on_insert_enter = function()
if config.enabled() then
+ cmp.config.compare.scopes:update()
+ cmp.config.compare.locality:update()
cmp.core:prepare()
cmp.core:on_change('InsertEnter')
end
-end)
+end
+autocmd.subscribe({ 'CmdlineEnter' }, async.debounce_next_tick(on_insert_enter))
+autocmd.subscribe({ 'InsertEnter' }, async.debounce_next_tick_by_keymap(on_insert_enter))
-autocmd.subscribe('CmdlineLeave', function()
- cmp.core:reset()
- cmp.core.view:close()
-end)
-
-autocmd.subscribe('TextChanged', function()
+-- async.throttle is needed for performance. The mapping `:...` will fire `CmdlineChanged` for each character.
+local on_text_changed = function()
if config.enabled() then
cmp.core:on_change('TextChanged')
end
-end)
+end
+autocmd.subscribe({ 'TextChangedI', 'TextChangedP' }, on_text_changed)
+autocmd.subscribe('CmdlineChanged', async.debounce_next_tick(on_text_changed))
-autocmd.subscribe('CursorMoved', function()
+autocmd.subscribe('CursorMovedI', function()
if config.enabled() then
cmp.core:on_moved()
else
@@ -323,9 +325,10 @@ autocmd.subscribe('CursorMoved', function()
end
end)
-autocmd.subscribe('InsertEnter', function()
- cmp.config.compare.scopes:update()
- cmp.config.compare.locality:update()
+-- If make this asynchronous, the completion menu will not close when the command output is displayed.
+autocmd.subscribe({ 'InsertLeave', 'CmdlineLeave' }, function()
+ cmp.core:reset()
+ cmp.core.view:close()
end)
cmp.event:on('complete_done', function(evt)
diff --git a/bundle/nvim-cmp/lua/cmp/matcher.lua b/bundle/nvim-cmp/lua/cmp/matcher.lua
index 7649f047b..26b3d1f27 100644
--- a/bundle/nvim-cmp/lua/cmp/matcher.lua
+++ b/bundle/nvim-cmp/lua/cmp/matcher.lua
@@ -66,14 +66,20 @@ end
--
-- `candlesingle` -> candle#accept#single
-- ^^^^^^~~~~~~ ^^^^^^ ~~~~~~
---
-- * The `accept`'s `a` should not match to `candle`'s `a`
--
+-- 7. Avoid false positive matching
+--
+-- `,` -> print,
+-- ~
+-- * Typically, the middle match with symbol characters only is false positive. should be ignored.
+--
+--
---Match entry
---@param input string
---@param word string
----@param option { synonyms: string[], disallow_fuzzy_matching: boolean, disallow_partial_matching: boolean, disallow_prefix_unmatching: boolean }
----@return number
+---@param option { synonyms: string[], disallow_fullfuzzy_matching: boolean, disallow_fuzzy_matching: boolean, disallow_partial_fuzzy_matching: boolean, disallow_partial_matching: boolean, disallow_prefix_unmatching: boolean }
+---@return integer
matcher.match = function(input, word, option)
option = option or {}
@@ -100,12 +106,14 @@ matcher.match = function(input, word, option)
local input_end_index = 1
local word_index = 1
local word_bound_index = 1
+ local no_symbol_match = false
while input_end_index <= #input and word_index <= #word do
local m = matcher.find_match_region(input, input_start_index, input_end_index, word, word_index)
if m and input_end_index <= m.input_match_end then
m.index = word_bound_index
input_start_index = m.input_match_start + 1
input_end_index = m.input_match_end + 1
+ no_symbol_match = no_symbol_match or m.no_symbol_match
word_index = char.get_next_semantic_index(word, m.word_match_end)
table.insert(matches, m)
else
@@ -120,6 +128,11 @@ matcher.match = function(input, word, option)
end
if #matches == 0 then
+ if not option.disallow_fuzzy_matching and not option.disallow_prefix_unmatching and not option.disallow_partial_fuzzy_matching then
+ if matcher.fuzzy(input, word, matches, option) then
+ return 1, matches
+ end
+ end
return 0, {}
end
@@ -146,6 +159,10 @@ matcher.match = function(input, word, option)
end
end
+ if no_symbol_match and not prefix then
+ return 0, {}
+ end
+
-- Compute prefix match score
local score = prefix and matcher.PREFIX_FACTOR or 0
local offset = prefix and matches[1].index - 1 or 0
@@ -167,8 +184,10 @@ matcher.match = function(input, word, option)
-- Check remaining input as fuzzy
if matches[#matches].input_match_end < #input then
if not option.disallow_fuzzy_matching then
- if prefix and matcher.fuzzy(input, word, matches) then
- return score, matches
+ if not option.disallow_partial_fuzzy_matching or prefix then
+ if matcher.fuzzy(input, word, matches, option) then
+ return score, matches
+ end
end
end
return 0, {}
@@ -178,11 +197,10 @@ matcher.match = function(input, word, option)
end
--- fuzzy
-matcher.fuzzy = function(input, word, matches)
- local last_match = matches[#matches]
+matcher.fuzzy = function(input, word, matches, option)
+ local input_index = matches[#matches] and (matches[#matches].input_match_end + 1) or 1
-- Lately specified middle of text.
- local input_index = last_match.input_match_end + 1
for i = 1, #matches - 1 do
local curr_match = matches[i]
local next_match = matches[i + 1]
@@ -200,10 +218,9 @@ matcher.fuzzy = function(input, word, matches)
end
-- Remaining text fuzzy match.
- local last_input_index = input_index
local matched = false
local word_offset = 0
- local word_index = last_match.word_match_end + 1
+ local word_index = matches[#matches] and (matches[#matches].word_match_end + 1) or 1
local input_match_start = -1
local input_match_end = -1
local word_match_start = -1
@@ -220,12 +237,26 @@ matcher.fuzzy = function(input, word, matches)
input_index = input_index + 1
strict_count = strict_count + (c1 == c2 and 1 or 0)
match_count = match_count + 1
- elseif matched then
- input_index = last_input_index
- input_match_end = input_index - 1
+ else
+ if option.disallow_fullfuzzy_matching then
+ break
+ else
+ if matched then
+ table.insert(matches, {
+ input_match_start = input_match_start,
+ input_match_end = input_index - 1,
+ word_match_start = word_match_start,
+ word_match_end = word_index + word_offset - 1,
+ strict_ratio = strict_count / match_count,
+ fuzzy = true,
+ })
+ end
+ end
+ matched = false
end
word_offset = word_offset + 1
end
+
if input_index > #input then
table.insert(matches, {
input_match_start = input_match_start,
@@ -260,6 +291,7 @@ matcher.find_match_region = function(input, input_start_index, input_end_index,
local word_offset = 0
local strict_count = 0
local match_count = 0
+ local no_symbol_match = false
while input_index <= #input and word_index + word_offset <= #word do
local c1 = string.byte(input, input_index)
local c2 = string.byte(word, word_index + word_offset)
@@ -272,6 +304,7 @@ matcher.find_match_region = function(input, input_start_index, input_end_index,
strict_count = strict_count + (c1 == c2 and 1 or 0)
match_count = match_count + 1
word_offset = word_offset + 1
+ no_symbol_match = no_symbol_match or char.is_symbol(c1)
else
-- Match end (partial region)
if input_match_start ~= -1 then
@@ -281,6 +314,7 @@ matcher.find_match_region = function(input, input_start_index, input_end_index,
word_match_start = word_index,
word_match_end = word_index + word_offset - 1,
strict_ratio = strict_count / match_count,
+ no_symbol_match = no_symbol_match,
fuzzy = false,
}
else
@@ -298,6 +332,7 @@ matcher.find_match_region = function(input, input_start_index, input_end_index,
word_match_start = word_index,
word_match_end = word_index + word_offset - 1,
strict_ratio = strict_count / match_count,
+ no_symbol_match = no_symbol_match,
fuzzy = false,
}
end
diff --git a/bundle/nvim-cmp/lua/cmp/matcher_spec.lua b/bundle/nvim-cmp/lua/cmp/matcher_spec.lua
index 768988e84..cb753df92 100644
--- a/bundle/nvim-cmp/lua/cmp/matcher_spec.lua
+++ b/bundle/nvim-cmp/lua/cmp/matcher_spec.lua
@@ -28,8 +28,35 @@ describe('matcher', function()
assert.is.truthy(matcher.match('my_', 'my_awesome_variable') > matcher.match('my_', 'completion_matching_strategy_list'))
assert.is.truthy(matcher.match('2', '[[2021') >= 1)
+ assert.is.truthy(matcher.match(',', 'pri,') == 0)
+ assert.is.truthy(matcher.match('/', '/**') >= 1)
+
assert.is.truthy(matcher.match('true', 'v:true', { synonyms = { 'true' } }) == matcher.match('true', 'true'))
assert.is.truthy(matcher.match('g', 'get', { synonyms = { 'get' } }) > matcher.match('g', 'dein#get', { 'dein#get' }))
+
+ assert.is.truthy(matcher.match('Unit', 'net.UnixListener', { disallow_partial_fuzzy_matching = true }) == 0)
+ assert.is.truthy(matcher.match('Unit', 'net.UnixListener', { disallow_partial_fuzzy_matching = false }) >= 1)
+
+ assert.is.truthy(matcher.match('emg', 'error_msg') >= 1)
+ assert.is.truthy(matcher.match('sasr', 'saved_splitright') >= 1)
+
+ local score, matches
+ score, matches = matcher.match('tail', 'HCDetails', {
+ disallow_fuzzy_matching = false,
+ disallow_partial_matching = false,
+ disallow_prefix_unmatching = false,
+ disallow_partial_fuzzy_matching = false,
+ })
+ assert.is.truthy(score >= 1)
+ assert.equals(matches[1].word_match_start, 5)
+
+ score = matcher.match('tail', 'HCDetails', {
+ disallow_fuzzy_matching = false,
+ disallow_partial_matching = false,
+ disallow_prefix_unmatching = false,
+ disallow_partial_fuzzy_matching = true,
+ })
+ assert.is.truthy(score == 0)
end)
it('disallow_fuzzy_matching', function()
@@ -37,6 +64,11 @@ describe('matcher', function()
assert.is.truthy(matcher.match('fmodify', 'fnamemodify', { disallow_fuzzy_matching = false }) >= 1)
end)
+ it('disallow_fullfuzzy_matching', function()
+ assert.is.truthy(matcher.match('svd', 'saved_splitright', { disallow_fullfuzzy_matching = true }) == 0)
+ assert.is.truthy(matcher.match('svd', 'saved_splitright', { disallow_fullfuzzy_matching = false }) >= 1)
+ end)
+
it('disallow_partial_matching', function()
assert.is.truthy(matcher.match('fb', 'foo_bar', { disallow_partial_matching = true }) == 0)
assert.is.truthy(matcher.match('fb', 'foo_bar', { disallow_partial_matching = false }) >= 1)
diff --git a/bundle/nvim-cmp/lua/cmp/source.lua b/bundle/nvim-cmp/lua/cmp/source.lua
index b73d43e05..9b8f662ce 100644
--- a/bundle/nvim-cmp/lua/cmp/source.lua
+++ b/bundle/nvim-cmp/lua/cmp/source.lua
@@ -10,23 +10,23 @@ local pattern = require('cmp.utils.pattern')
local char = require('cmp.utils.char')
---@class cmp.Source
----@field public id number
+---@field public id integer
---@field public name string
---@field public source any
---@field public cache cmp.Cache
----@field public revision number
+---@field public revision integer
---@field public incomplete boolean
---@field public is_triggered_by_symbol boolean
---@field public entries cmp.Entry[]
----@field public offset number
----@field public request_offset number
+---@field public offset integer
+---@field public request_offset integer
---@field public context cmp.Context
---@field public completion_context lsp.CompletionContext|nil
---@field public status cmp.SourceStatus
---@field public complete_dedup function
local source = {}
----@alias cmp.SourceStatus "1" | "2" | "3"
+---@alias cmp.SourceStatus 1 | 2 | 3
source.SourceStatus = {}
source.SourceStatus.WAITING = 1
source.SourceStatus.FETCHING = 2
@@ -46,7 +46,6 @@ source.new = function(name, s)
end
---Reset current completion state
----@return boolean
source.reset = function(self)
self.cache:clear()
self.revision = self.revision + 1
@@ -89,86 +88,92 @@ source.get_entries = function(self, ctx)
return {}
end
- local target_entries = (function()
- local key = { 'get_entries', self.revision }
- for i = ctx.cursor.col, self.offset, -1 do
- key[3] = string.sub(ctx.cursor_before_line, 1, i)
- local prev_entries = self.cache:get(key)
- if prev_entries then
- return prev_entries
- end
+ local target_entries = self.entries
+
+ local prev = self.cache:get({ 'get_entries', tostring(self.revision) })
+ if prev and ctx.cursor.row == prev.ctx.cursor.row and self.offset == prev.offset then
+ if ctx.cursor.col == prev.ctx.cursor.col then
+ return prev.entries
end
- return self.entries
- end)()
+ -- only use prev entries when cursor is moved forward.
+ -- and the pattern offset is the same.
+ if prev.ctx.cursor.col <= ctx.cursor.col then
+ target_entries = prev.entries
+ end
+ end
+
+ local entry_filter = self:get_entry_filter()
local inputs = {}
+ ---@type cmp.Entry[]
local entries = {}
+ local matching_config = self:get_matching_config()
for _, e in ipairs(target_entries) do
local o = e:get_offset()
if not inputs[o] then
inputs[o] = string.sub(ctx.cursor_before_line, o)
end
- local match = e:match(inputs[o], self:get_matching_config())
+ local match = e:match(inputs[o], matching_config)
e.score = match.score
e.exact = false
if e.score >= 1 then
e.matches = match.matches
e.exact = e:get_filter_text() == inputs[o] or e:get_word() == inputs[o]
- table.insert(entries, e)
- end
- end
- self.cache:set({ 'get_entries', self.revision, ctx.cursor_before_line }, entries)
- local max_item_count = self:get_source_config().max_item_count or 200
- local limited_entries = {}
- for _, e in ipairs(entries) do
- table.insert(limited_entries, e)
- if max_item_count and #limited_entries >= max_item_count then
- break
+ if entry_filter(e, ctx) then
+ entries[#entries + 1] = e
+ end
+ end
+ async.yield()
+ if ctx.aborted then
+ async.abort()
end
end
- return limited_entries
+
+ self.cache:set({ 'get_entries', tostring(self.revision) }, { entries = entries, ctx = ctx, offset = self.offset })
+
+ return entries
end
----Get default insert range
----@return lsp.Range|nil
+---Get default insert range (UTF8 byte index).
+---@return lsp.Range
source.get_default_insert_range = function(self)
if not self.context then
- return nil
+ error('context is not initialized yet.')
end
- return self.cache:ensure({ 'get_default_insert_range', self.revision }, function()
+ return self.cache:ensure({ 'get_default_insert_range', tostring(self.revision) }, function()
return {
start = {
line = self.context.cursor.row - 1,
- character = misc.to_utfindex(self.context.cursor_line, self.offset),
+ character = self.offset - 1,
},
['end'] = {
line = self.context.cursor.row - 1,
- character = misc.to_utfindex(self.context.cursor_line, self.context.cursor.col),
+ character = self.context.cursor.col - 1,
},
}
end)
end
----Get default replace range
----@return lsp.Range|nil
+---Get default replace range (UTF8 byte index).
+---@return lsp.Range
source.get_default_replace_range = function(self)
if not self.context then
- return nil
+ error('context is not initialized yet.')
end
- return self.cache:ensure({ 'get_default_replace_range', self.revision }, function()
+ return self.cache:ensure({ 'get_default_replace_range', tostring(self.revision) }, function()
local _, e = pattern.offset('^' .. '\\%(' .. self:get_keyword_pattern() .. '\\)', string.sub(self.context.cursor_line, self.offset))
return {
start = {
line = self.context.cursor.row - 1,
- character = misc.to_utfindex(self.context.cursor_line, self.offset),
+ character = self.offset,
},
['end'] = {
line = self.context.cursor.row - 1,
- character = misc.to_utfindex(self.context.cursor_line, e and self.offset + e - 1 or self.context.cursor.col),
+ character = (e and self.offset + e - 2 or self.context.cursor.col - 1),
},
}
end)
@@ -217,13 +222,16 @@ source.get_keyword_pattern = function(self)
return c.keyword_pattern
end
if self.source.get_keyword_pattern then
- return self.source:get_keyword_pattern(misc.copy(c))
+ local keyword_pattern = self.source:get_keyword_pattern(misc.copy(c))
+ if keyword_pattern then
+ return keyword_pattern
+ end
end
return config.get().completion.keyword_pattern
end
---Get keyword_length
----@return number
+---@return integer
source.get_keyword_length = function(self)
local c = self:get_source_config()
if c.keyword_length then
@@ -232,10 +240,31 @@ source.get_keyword_length = function(self)
return config.get().completion.keyword_length or 1
end
+---Get filter
+--@return fun(entry: cmp.Entry, context: cmp.Context): boolean
+source.get_entry_filter = function(self)
+ local c = self:get_source_config()
+ if c.entry_filter then
+ return c.entry_filter --[[@as fun(entry: cmp.Entry, context: cmp.Context): boolean]]
+ end
+ return function(_, _)
+ return true
+ end
+end
+
+---Get lsp.PositionEncodingKind
+---@return lsp.PositionEncodingKind
+source.get_position_encoding_kind = function(self)
+ if self.source.get_position_encoding_kind then
+ return self.source:get_position_encoding_kind()
+ end
+ return types.lsp.PositionEncodingKind.UTF16
+end
+
---Invoke completion
---@param ctx cmp.Context
---@param callback function
----@return boolean Return true if not trigger completion.
+---@return boolean? Return true if not trigger completion.
source.complete = function(self, ctx, callback)
local offset = ctx:get_offset(self:get_keyword_pattern())
@@ -260,7 +289,7 @@ source.complete = function(self, ctx, callback)
triggerCharacter = before_char,
}
elseif ctx:get_reason() ~= types.cmp.ContextReason.TriggerOnly then
- if self:get_keyword_length() <= (ctx.cursor.col - offset) then
+ if offset < ctx.cursor.col and self:get_keyword_length() <= (ctx.cursor.col - offset) then
if self.incomplete and self.context.cursor.col ~= ctx.cursor.col and self.status ~= source.SourceStatus.FETCHING then
completion_context = {
triggerKind = types.lsp.CompletionTriggerKind.TriggerForIncompleteCompletions,
@@ -300,6 +329,10 @@ source.complete = function(self, ctx, callback)
completion_context = completion_context,
}),
self.complete_dedup(vim.schedule_wrap(function(response)
+ if self.context ~= ctx then
+ return
+ end
+ ---@type lsp.CompletionResponse
response = response or {}
self.incomplete = response.isIncomplete or false
@@ -312,14 +345,14 @@ source.complete = function(self, ctx, callback)
self.status = source.SourceStatus.COMPLETED
self.entries = {}
for i, item in ipairs(response.items or response) do
- if (misc.safe(item) or {}).label then
- local e = entry.new(ctx, self, item)
+ if (item or {}).label then
+ local e = entry.new(ctx, self, item, response.itemDefaults)
self.entries[i] = e
self.offset = math.min(self.offset, e:get_offset())
end
end
self.revision = self.revision + 1
- if #self:get_entries(ctx) == 0 then
+ if #self.entries == 0 then
self.offset = old_offset
self.entries = old_entries
self.revision = self.revision + 1
diff --git a/bundle/nvim-cmp/lua/cmp/types/cmp.lua b/bundle/nvim-cmp/lua/cmp/types/cmp.lua
index b4e940273..e3bf8e6e3 100644
--- a/bundle/nvim-cmp/lua/cmp/types/cmp.lua
+++ b/bundle/nvim-cmp/lua/cmp/types/cmp.lua
@@ -1,37 +1,43 @@
local cmp = {}
----@alias cmp.ConfirmBehavior "'insert'" | "'replace'"
-cmp.ConfirmBehavior = {}
-cmp.ConfirmBehavior.Insert = 'insert'
-cmp.ConfirmBehavior.Replace = 'replace'
+---@alias cmp.ConfirmBehavior 'insert' | 'replace'
+cmp.ConfirmBehavior = {
+ Insert = 'insert',
+ Replace = 'replace',
+}
----@alias cmp.SelectBehavior "'insert'" | "'select'"
-cmp.SelectBehavior = {}
-cmp.SelectBehavior.Insert = 'insert'
-cmp.SelectBehavior.Select = 'select'
+---@alias cmp.SelectBehavior 'insert' | 'select'
+cmp.SelectBehavior = {
+ Insert = 'insert',
+ Select = 'select',
+}
----@alias cmp.ContextReason "'auto'" | "'manual'" | "'none'"
-cmp.ContextReason = {}
-cmp.ContextReason.Auto = 'auto'
-cmp.ContextReason.Manual = 'manual'
-cmp.ContextReason.TriggerOnly = 'triggerOnly'
-cmp.ContextReason.None = 'none'
+---@alias cmp.ContextReason 'auto' | 'manual' | 'triggerOnly' | 'none'
+cmp.ContextReason = {
+ Auto = 'auto',
+ Manual = 'manual',
+ TriggerOnly = 'triggerOnly',
+ None = 'none',
+}
----@alias cmp.TriggerEvent "'InsertEnter'" | "'TextChanged'"
-cmp.TriggerEvent = {}
-cmp.TriggerEvent.InsertEnter = 'InsertEnter'
-cmp.TriggerEvent.TextChanged = 'TextChanged'
+---@alias cmp.TriggerEvent 'InsertEnter' | 'TextChanged'
+cmp.TriggerEvent = {
+ InsertEnter = 'InsertEnter',
+ TextChanged = 'TextChanged',
+}
----@alias cmp.PreselectMode "'item'" | "'None'"
-cmp.PreselectMode = {}
-cmp.PreselectMode.Item = 'item'
-cmp.PreselectMode.None = 'none'
+---@alias cmp.PreselectMode 'item' | 'None'
+cmp.PreselectMode = {
+ Item = 'item',
+ None = 'none',
+}
----@alias cmp.ItemField "'abbr'" | "'kind'" | "'menu'"
-cmp.ItemField = {}
-cmp.ItemField.Abbr = 'abbr'
-cmp.ItemField.Kind = 'kind'
-cmp.ItemField.Menu = 'menu'
+---@alias cmp.ItemField 'abbr' | 'kind' | 'menu'
+cmp.ItemField = {
+ Abbr = 'abbr',
+ Kind = 'kind',
+ Menu = 'menu',
+}
---@class cmp.ContextOption
---@field public reason cmp.ContextReason|nil
@@ -45,22 +51,24 @@ cmp.ItemField.Menu = 'menu'
---@class cmp.SnippetExpansionParams
---@field public body string
----@field public insert_text_mode number
+---@field public insert_text_mode integer
---@class cmp.CompleteParams
---@field public reason? cmp.ContextReason
---@field public config? cmp.ConfigSchema
----@class cmp.Setup
----@field public __call fun(c: cmp.ConfigSchema)
+---@class cmp.SetupProperty
---@field public buffer fun(c: cmp.ConfigSchema)
---@field public global fun(c: cmp.ConfigSchema)
----@field public cmdline fun(type: string, c: cmp.ConfigSchema)
+---@field public cmdline fun(type: string|string[], c: cmp.ConfigSchema)
+---@field public filetype fun(type: string|string[], c: cmp.ConfigSchema)
+
+---@alias cmp.Setup cmp.SetupProperty | fun(c: cmp.ConfigSchema)
---@class cmp.SourceApiParams: cmp.SourceConfig
---@class cmp.SourceCompletionApiParams : cmp.SourceConfig
----@field public offset number
+---@field public offset integer
---@field public context cmp.Context
---@field public completion_context lsp.CompletionContext
@@ -71,11 +79,12 @@ cmp.ItemField.Menu = 'menu'
---@field public s nil|function(fallback: function): void
---@class cmp.ConfigSchema
----@field private revision number
----@field public enabled fun():boolean|boolean
+---@field private revision integer
+---@field public enabled boolean | fun(): boolean
+---@field public performance cmp.PerformanceConfig
---@field public preselect cmp.PreselectMode
---@field public completion cmp.CompletionConfig
----@field public documentation cmp.DocumentationConfig|"false"
+---@field public window cmp.WindowConfig|nil
---@field public confirmation cmp.ConfirmationConfig
---@field public matching cmp.MatchingConfig
---@field public sorting cmp.SortingConfig
@@ -86,19 +95,32 @@ cmp.ItemField.Menu = 'menu'
---@field public view cmp.ViewConfig
---@field public experimental cmp.ExperimentalConfig
+---@class cmp.PerformanceConfig
+---@field public debounce integer
+---@field public throttle integer
+---@field public fetching_timeout integer
+---@field public async_budget integer Maximum time (in ms) an async function is allowed to run during one step of the event loop.
+---@field public max_view_entries integer
+
+---@class cmp.WindowConfig
+---@field completion cmp.WindowConfig
+---@field documentation cmp.WindowConfig|nil
+
---@class cmp.CompletionConfig
---@field public autocomplete cmp.TriggerEvent[]
---@field public completeopt string
----@field public keyword_pattern string
----@field public keyword_length number
---@field public get_trigger_characters fun(trigger_characters: string[]): string[]
+---@field public keyword_length integer
+---@field public keyword_pattern string
----@class cmp.DocumentationConfig
----@field public border string[]
+---@class cmp.WindowConfig
+---@field public border string|string[]
---@field public winhighlight string
----@field public maxwidth number|nil
----@field public maxheight number|nil
----@field public zindex number|nil
+---@field public zindex integer|nil
+---@field public max_width integer|nil
+---@field public max_height integer|nil
+---@field public scrolloff integer|nil
+---@field public scrollbar boolean|true
---@class cmp.ConfirmationConfig
---@field public default_behavior cmp.ConfirmBehavior
@@ -106,23 +128,25 @@ cmp.ItemField.Menu = 'menu'
---@class cmp.MatchingConfig
---@field public disallow_fuzzy_matching boolean
+---@field public disallow_fullfuzzy_matching boolean
+---@field public disallow_partial_fuzzy_matching boolean
---@field public disallow_partial_matching boolean
---@field public disallow_prefix_unmatching boolean
---@class cmp.SortingConfig
----@field public priority_weight number
+---@field public priority_weight integer
---@field public comparators function[]
---@class cmp.FormattingConfig
---@field public fields cmp.ItemField[]
+---@field public expandable_indicator boolean
---@field public format fun(entry: cmp.Entry, vim_item: vim.CompletedItem): vim.CompletedItem
---@class cmp.SnippetConfig
---@field public expand fun(args: cmp.SnippetExpansionParams)
---@class cmp.ExperimentalConfig
----@field public native_menu boolean
----@field public ghost_text cmp.GhostTextConfig|"false"
+---@field public ghost_text cmp.GhostTextConfig|false
---@class cmp.GhostTextConfig
---@field hl_group string
@@ -130,12 +154,12 @@ cmp.ItemField.Menu = 'menu'
---@class cmp.SourceConfig
---@field public name string
---@field public option table|nil
----@field public priority number|nil
+---@field public priority integer|nil
---@field public trigger_characters string[]|nil
---@field public keyword_pattern string|nil
----@field public keyword_length number|nil
----@field public max_item_count number|nil
----@field public group_index number|nil
+---@field public keyword_length integer|nil
+---@field public group_index integer|nil
+---@field public entry_filter nil|function(entry: cmp.Entry, ctx: cmp.Context): boolean
---@class cmp.ViewConfig
---@field public entries cmp.EntriesConfig
@@ -143,14 +167,14 @@ cmp.ItemField.Menu = 'menu'
---@alias cmp.EntriesConfig cmp.CustomEntriesConfig|cmp.NativeEntriesConfig|cmp.WildmenuEntriesConfig|string
---@class cmp.CustomEntriesConfig
----@field name "'custom'"
----@field selection_order "'top_down'"|"'near_cursor'"
+---@field name 'custom'
+---@field selection_order 'top_down'|'near_cursor'
---@class cmp.NativeEntriesConfig
----@field name "'native'"
+---@field name 'native'
---@class cmp.WildmenuEntriesConfig
----@field name "'wildmenu'"
+---@field name 'wildmenu'
---@field separator string|nil
return cmp
diff --git a/bundle/nvim-cmp/lua/cmp/types/lsp.lua b/bundle/nvim-cmp/lua/cmp/types/lsp.lua
index c8a04473d..a62e0e7e9 100644
--- a/bundle/nvim-cmp/lua/cmp/types/lsp.lua
+++ b/bundle/nvim-cmp/lua/cmp/types/lsp.lua
@@ -1,151 +1,234 @@
local misc = require('cmp.utils.misc')
+
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/
---@class lsp
local lsp = {}
-lsp.Position = {}
+---@enum lsp.PositionEncodingKind
+lsp.PositionEncodingKind = {
+ UTF8 = 'utf-8',
+ UTF16 = 'utf-16',
+ UTF32 = 'utf-32',
+}
----Convert lsp.Position to vim.Position
----@param buf number|string
----@param position lsp.Position
----@return vim.Position
-lsp.Position.to_vim = function(buf, position)
- if not vim.api.nvim_buf_is_loaded(buf) then
- vim.fn.bufload(buf)
- end
- local lines = vim.api.nvim_buf_get_lines(buf, position.line, position.line + 1, false)
- if #lines > 0 then
+lsp.Position = {
+ ---Convert lsp.Position to vim.Position
+ ---@param buf integer
+ ---@param position lsp.Position
+ --
+ ---@return vim.Position
+ to_vim = function(buf, position)
+ if not vim.api.nvim_buf_is_loaded(buf) then
+ vim.fn.bufload(buf)
+ end
+ local lines = vim.api.nvim_buf_get_lines(buf, position.line, position.line + 1, false)
+ if #lines > 0 then
+ return {
+ row = position.line + 1,
+ col = misc.to_vimindex(lines[1], position.character),
+ }
+ end
return {
row = position.line + 1,
- col = misc.to_vimindex(lines[1], position.character),
+ col = position.character + 1,
}
- end
- return {
- row = position.line + 1,
- col = position.character + 1,
- }
-end
-
----Convert vim.Position to lsp.Position
----@param buf number|string
----@param position vim.Position
----@return lsp.Position
-lsp.Position.to_lsp = function(buf, position)
- if not vim.api.nvim_buf_is_loaded(buf) then
- vim.fn.bufload(buf)
- end
- local lines = vim.api.nvim_buf_get_lines(buf, position.row - 1, position.row, false)
- if #lines > 0 then
+ end,
+ ---Convert vim.Position to lsp.Position
+ ---@param buf integer
+ ---@param position vim.Position
+ ---@return lsp.Position
+ to_lsp = function(buf, position)
+ if not vim.api.nvim_buf_is_loaded(buf) then
+ vim.fn.bufload(buf)
+ end
+ local lines = vim.api.nvim_buf_get_lines(buf, position.row - 1, position.row, false)
+ if #lines > 0 then
+ return {
+ line = position.row - 1,
+ character = misc.to_utfindex(lines[1], position.col),
+ }
+ end
return {
line = position.row - 1,
- character = misc.to_utfindex(lines[1], position.col),
+ character = position.col - 1,
}
- end
- return {
- line = position.row - 1,
- character = position.col - 1,
- }
-end
+ end,
-lsp.Range = {}
+ ---Convert position to utf8 from specified encoding.
+ ---@param text string
+ ---@param position lsp.Position
+ ---@param from_encoding? lsp.PositionEncodingKind
+ ---@return lsp.Position
+ to_utf8 = function(text, position, from_encoding)
+ from_encoding = from_encoding or lsp.PositionEncodingKind.UTF16
+ if from_encoding == lsp.PositionEncodingKind.UTF8 then
+ return position
+ end
----Convert lsp.Range to vim.Range
----@param buf number|string
----@param range lsp.Range
----@return vim.Range
-lsp.Range.to_vim = function(buf, range)
- return {
- start = lsp.Position.to_vim(buf, range.start),
- ['end'] = lsp.Position.to_vim(buf, range['end']),
- }
-end
+ local ok, byteindex = pcall(function()
+ return vim.str_byteindex(text, position.character, from_encoding == lsp.PositionEncodingKind.UTF16)
+ end)
+ if not ok then
+ return position
+ end
+ return { line = position.line, character = byteindex }
+ end,
----Convert vim.Range to lsp.Range
----@param buf number|string
----@param range vim.Range
----@return lsp.Range
-lsp.Range.to_lsp = function(buf, range)
- return {
- start = lsp.Position.to_lsp(buf, range.start),
- ['end'] = lsp.Position.to_lsp(buf, range['end']),
- }
-end
+ ---Convert position to utf16 from specified encoding.
+ ---@param text string
+ ---@param position lsp.Position
+ ---@param from_encoding? lsp.PositionEncodingKind
+ ---@return lsp.Position
+ to_utf16 = function(text, position, from_encoding)
+ from_encoding = from_encoding or lsp.PositionEncodingKind.UTF16
+ if from_encoding == lsp.PositionEncodingKind.UTF16 then
+ return position
+ end
----@alias lsp.CompletionTriggerKind "1" | "2" | "3"
-lsp.CompletionTriggerKind = {}
-lsp.CompletionTriggerKind.Invoked = 1
-lsp.CompletionTriggerKind.TriggerCharacter = 2
-lsp.CompletionTriggerKind.TriggerForIncompleteCompletions = 3
+ local utf8 = lsp.Position.to_utf8(text, position, from_encoding)
+ for index = utf8.character, 0, -1 do
+ local ok, utf16index = pcall(function()
+ return select(2, vim.str_utfindex(text, index))
+ end)
+ if ok then
+ return { line = utf8.line, character = utf16index }
+ end
+ end
+ return position
+ end,
+
+ ---Convert position to utf32 from specified encoding.
+ ---@param text string
+ ---@param position lsp.Position
+ ---@param from_encoding? lsp.PositionEncodingKind
+ ---@return lsp.Position
+ to_utf32 = function(text, position, from_encoding)
+ from_encoding = from_encoding or lsp.PositionEncodingKind.UTF16
+ if from_encoding == lsp.PositionEncodingKind.UTF32 then
+ return position
+ end
+
+ local utf8 = lsp.Position.to_utf8(text, position, from_encoding)
+ for index = utf8.character, 0, -1 do
+ local ok, utf32index = pcall(function()
+ return select(1, vim.str_utfindex(text, index))
+ end)
+ if ok then
+ return { line = utf8.line, character = utf32index }
+ end
+ end
+ return position
+ end,
+}
+
+lsp.Range = {
+ ---Convert lsp.Range to vim.Range
+ ---@param buf integer
+ ---@param range lsp.Range
+ ---@return vim.Range
+ to_vim = function(buf, range)
+ return {
+ start = lsp.Position.to_vim(buf, range.start),
+ ['end'] = lsp.Position.to_vim(buf, range['end']),
+ }
+ end,
+
+ ---Convert vim.Range to lsp.Range
+ ---@param buf integer
+ ---@param range vim.Range
+ ---@return lsp.Range
+ to_lsp = function(buf, range)
+ return {
+ start = lsp.Position.to_lsp(buf, range.start),
+ ['end'] = lsp.Position.to_lsp(buf, range['end']),
+ }
+ end,
+}
+
+---@alias lsp.CompletionTriggerKind 1 | 2 | 3
+lsp.CompletionTriggerKind = {
+ Invoked = 1,
+ TriggerCharacter = 2,
+ TriggerForIncompleteCompletions = 3,
+}
+
+---@alias lsp.InsertTextFormat 1 | 2
+lsp.InsertTextFormat = {}
+lsp.InsertTextFormat.PlainText = 1
+lsp.InsertTextFormat.Snippet = 2
+
+---@alias lsp.InsertTextMode 1 | 2
+lsp.InsertTextMode = {
+ AsIs = 1,
+ AdjustIndentation = 2,
+}
+
+---@alias lsp.MarkupKind 'plaintext' | 'markdown'
+lsp.MarkupKind = {
+ PlainText = 'plaintext',
+ Markdown = 'markdown',
+}
+
+---@alias lsp.CompletionItemTag 1
+lsp.CompletionItemTag = {
+ Deprecated = 1,
+}
+
+---@alias lsp.CompletionItemKind 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25
+lsp.CompletionItemKind = {
+ Text = 1,
+ Method = 2,
+ Function = 3,
+ Constructor = 4,
+ Field = 5,
+ Variable = 6,
+ Class = 7,
+ Interface = 8,
+ Module = 9,
+ Property = 10,
+ Unit = 11,
+ Value = 12,
+ Enum = 13,
+ Keyword = 14,
+ Snippet = 15,
+ Color = 16,
+ File = 17,
+ Reference = 18,
+ Folder = 19,
+ EnumMember = 20,
+ Constant = 21,
+ Struct = 22,
+ Event = 23,
+ Operator = 24,
+ TypeParameter = 25,
+}
+lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind)
+
+---@class lsp.internal.CompletionItemDefaults
+---@field public commitCharacters? string[]
+---@field public editRange? lsp.Range | { insert: lsp.Range, replace: lsp.Range }
+---@field public insertTextFormat? lsp.InsertTextFormat
+---@field public insertTextMode? lsp.InsertTextMode
+---@field public data? any
---@class lsp.CompletionContext
---@field public triggerKind lsp.CompletionTriggerKind
---@field public triggerCharacter string|nil
----@alias lsp.InsertTextFormat "1" | "2"
-lsp.InsertTextFormat = {}
-lsp.InsertTextFormat.PlainText = 1
-lsp.InsertTextFormat.Snippet = 2
-lsp.InsertTextFormat = vim.tbl_add_reverse_lookup(lsp.InsertTextFormat)
-
----@alias lsp.InsertTextMode "1" | "2"
-lsp.InsertTextMode = {}
-lsp.InsertTextMode.AsIs = 0
-lsp.InsertTextMode.AdjustIndentation = 1
-lsp.InsertTextMode = vim.tbl_add_reverse_lookup(lsp.InsertTextMode)
-
----@alias lsp.MarkupKind "'plaintext'" | "'markdown'"
-lsp.MarkupKind = {}
-lsp.MarkupKind.PlainText = 'plaintext'
-lsp.MarkupKind.Markdown = 'markdown'
-lsp.MarkupKind = vim.tbl_add_reverse_lookup(lsp.MarkupKind)
-
----@alias lsp.CompletionItemTag "1"
-lsp.CompletionItemTag = {}
-lsp.CompletionItemTag.Deprecated = 1
-lsp.CompletionItemTag = vim.tbl_add_reverse_lookup(lsp.CompletionItemTag)
-
----@alias lsp.CompletionItemKind "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "20" | "21" | "22" | "23" | "24" | "25"
-lsp.CompletionItemKind = {}
-lsp.CompletionItemKind.Text = 1
-lsp.CompletionItemKind.Method = 2
-lsp.CompletionItemKind.Function = 3
-lsp.CompletionItemKind.Constructor = 4
-lsp.CompletionItemKind.Field = 5
-lsp.CompletionItemKind.Variable = 6
-lsp.CompletionItemKind.Class = 7
-lsp.CompletionItemKind.Interface = 8
-lsp.CompletionItemKind.Module = 9
-lsp.CompletionItemKind.Property = 10
-lsp.CompletionItemKind.Unit = 11
-lsp.CompletionItemKind.Value = 12
-lsp.CompletionItemKind.Enum = 13
-lsp.CompletionItemKind.Keyword = 14
-lsp.CompletionItemKind.Snippet = 15
-lsp.CompletionItemKind.Color = 16
-lsp.CompletionItemKind.File = 17
-lsp.CompletionItemKind.Reference = 18
-lsp.CompletionItemKind.Folder = 19
-lsp.CompletionItemKind.EnumMember = 20
-lsp.CompletionItemKind.Constant = 21
-lsp.CompletionItemKind.Struct = 22
-lsp.CompletionItemKind.Event = 23
-lsp.CompletionItemKind.Operator = 24
-lsp.CompletionItemKind.TypeParameter = 25
-lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind)
-
---@class lsp.CompletionList
---@field public isIncomplete boolean
+---@field public itemDefaults? lsp.internal.CompletionItemDefaults
---@field public items lsp.CompletionItem[]
----@alias lsp.CompletionResponse lsp.CompletionList|lsp.CompletionItem[]|nil
+---@alias lsp.CompletionResponse lsp.CompletionList|lsp.CompletionItem[]
---@class lsp.MarkupContent
---@field public kind lsp.MarkupKind
---@field public value string
---@class lsp.Position
----@field public line number
----@field public character number
+---@field public line integer
+---@field public character integer
---@class lsp.Range
---@field public start lsp.Position
@@ -160,34 +243,45 @@ lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind)
---@field public range lsp.Range|nil
---@field public newText string
----@class lsp.InsertReplaceTextEdit
----@field public insert lsp.Range|nil
----@field public replace lsp.Range|nil
+---@alias lsp.InsertReplaceTextEdit lsp.internal.InsertTextEdit|lsp.internal.ReplaceTextEdit
+
+---@class lsp.internal.InsertTextEdit
+---@field public insert lsp.Range
+---@field public newText string
+
+---@class lsp.internal.ReplaceTextEdit
+---@field public replace lsp.Range
---@field public newText string
---@class lsp.CompletionItemLabelDetails
----@field public detail string|nil
----@field public description string|nil
+---@field public detail? string
+---@field public description? string
+
+---@class lsp.internal.CmpCompletionExtension
+---@field public kind_text string
+---@field public kind_hl_group string
---@class lsp.CompletionItem
---@field public label string
----@field public labelDetails lsp.CompletionItemLabelDetails|nil
----@field public kind lsp.CompletionItemKind|nil
----@field public tags lsp.CompletionItemTag[]|nil
----@field public detail string|nil
----@field public documentation lsp.MarkupContent|string|nil
----@field public deprecated boolean|nil
----@field public preselect boolean|nil
----@field public sortText string|nil
----@field public filterText string|nil
----@field public insertText string|nil
----@field public insertTextFormat lsp.InsertTextFormat
----@field public insertTextMode lsp.InsertTextMode
----@field public textEdit lsp.TextEdit|lsp.InsertReplaceTextEdit|nil
----@field public additionalTextEdits lsp.TextEdit[]
----@field public commitCharacters string[]|nil
----@field public command lsp.Command|nil
----@field public data any|nil
+---@field public labelDetails? lsp.CompletionItemLabelDetails
+---@field public kind? lsp.CompletionItemKind
+---@field public tags? lsp.CompletionItemTag[]
+---@field public detail? string
+---@field public documentation? lsp.MarkupContent|string
+---@field public deprecated? boolean
+---@field public preselect? boolean
+---@field public sortText? string
+---@field public filterText? string
+---@field public insertText? string
+---@field public insertTextFormat? lsp.InsertTextFormat
+---@field public insertTextMode? lsp.InsertTextMode
+---@field public textEdit? lsp.TextEdit|lsp.InsertReplaceTextEdit
+---@field public textEditText? string
+---@field public additionalTextEdits? lsp.TextEdit[]
+---@field public commitCharacters? string[]
+---@field public command? lsp.Command
+---@field public data? any
+---@field public cmp? lsp.internal.CmpCompletionExtension
---
---TODO: Should send the issue for upstream?
---@field public word string|nil
diff --git a/bundle/nvim-cmp/lua/cmp/types/vim.lua b/bundle/nvim-cmp/lua/cmp/types/vim.lua
index 0ae28c69f..8532534c6 100644
--- a/bundle/nvim-cmp/lua/cmp/types/vim.lua
+++ b/bundle/nvim-cmp/lua/cmp/types/vim.lua
@@ -3,17 +3,17 @@
---@field public abbr string|nil
---@field public kind string|nil
---@field public menu string|nil
----@field public equal "1"|nil
----@field public empty "1"|nil
----@field public dup "1"|nil
+---@field public equal 1|nil
+---@field public empty 1|nil
+---@field public dup 1|nil
---@field public id any
---@field public abbr_hl_group string|nil
---@field public kind_hl_group string|nil
---@field public menu_hl_group string|nil
----@class vim.Position
----@field public row number
----@field public col number
+---@class vim.Position 1-based index
+---@field public row integer
+---@field public col integer
---@class vim.Range
---@field public start vim.Position
diff --git a/bundle/nvim-cmp/lua/cmp/utils/api.lua b/bundle/nvim-cmp/lua/cmp/utils/api.lua
index d0534092b..f304a78e3 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/api.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/api.lua
@@ -44,9 +44,10 @@ api.get_current_line = function()
return vim.api.nvim_get_current_line()
end
+---@return { [1]: integer, [2]: integer }
api.get_cursor = function()
if api.is_cmdline_mode() then
- return { vim.o.lines - (vim.api.nvim_get_option('cmdheight') or 1) + 1, vim.fn.getcmdpos() - 1 }
+ return { math.min(vim.o.lines, vim.o.lines - (vim.api.nvim_get_option('cmdheight') - 1)), vim.fn.getcmdpos() - 1 }
end
return vim.api.nvim_win_get_cursor(0)
end
@@ -54,7 +55,7 @@ end
api.get_screen_cursor = function()
if api.is_cmdline_mode() then
local cursor = api.get_cursor()
- return { cursor[1], cursor[2] + 1 }
+ return { cursor[1], vim.fn.strdisplaywidth(string.sub(vim.fn.getcmdline(), 1, cursor[2] + 1)) }
end
local cursor = api.get_cursor()
local pos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1)
diff --git a/bundle/nvim-cmp/lua/cmp/utils/api_spec.lua b/bundle/nvim-cmp/lua/cmp/utils/api_spec.lua
index 5363b485b..31bbee244 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/api_spec.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/api_spec.lua
@@ -4,8 +4,8 @@ local feedkeys = require('cmp.utils.feedkeys')
local api = require('cmp.utils.api')
describe('api', function()
+ before_each(spec.before)
describe('get_cursor', function()
- before_each(spec.before)
it('insert-mode', function()
local cursor
feedkeys.call(keymap.t('i\t1234567890'), 'nx', function()
@@ -24,8 +24,26 @@ describe('api', function()
end)
end)
+ describe('get_screen_cursor', function()
+ it('insert-mode', function()
+ local screen_cursor
+ feedkeys.call(keymap.t('iあいうえお'), 'nx', function()
+ screen_cursor = api.get_screen_cursor()
+ end)
+ assert.are.equal(10, screen_cursor[2])
+ end)
+ it('cmdline-mode', function()
+ local screen_cursor
+ keymap.set_map(0, 'c', '(cmp-spec-spy)', function()
+ screen_cursor = api.get_screen_cursor()
+ end, { expr = true, noremap = true })
+ feedkeys.call(keymap.t(':あいうえお'), 'n')
+ feedkeys.call(keymap.t('(cmp-spec-spy)'), 'x')
+ assert.are.equal(10, screen_cursor[2])
+ end)
+ end)
+
describe('get_cursor_before_line', function()
- before_each(spec.before)
it('insert-mode', function()
local cursor_before_line
feedkeys.call(keymap.t('i\t1234567890'), 'nx', function()
diff --git a/bundle/nvim-cmp/lua/cmp/utils/async.lua b/bundle/nvim-cmp/lua/cmp/utils/async.lua
index 8c698e735..c62b565ee 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/async.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/async.lua
@@ -1,18 +1,37 @@
+local feedkeys = require('cmp.utils.feedkeys')
+local config = require('cmp.config')
+
local async = {}
---@class cmp.AsyncThrottle
---@field public running boolean
----@field public timeout number
----@field public sync function(self: cmp.AsyncThrottle, timeout: number|nil)
+---@field public timeout integer
+---@field public sync function(self: cmp.AsyncThrottle, timeout: integer|nil)
---@field public stop function
---@field public __call function
+---@type uv_timer_t[]
+local timers = {}
+
+vim.api.nvim_create_autocmd('VimLeavePre', {
+ callback = function()
+ for _, timer in pairs(timers) do
+ if timer and not timer:is_closing() then
+ timer:stop()
+ timer:close()
+ end
+ end
+ end,
+})
+
---@param fn function
----@param timeout number
+---@param timeout integer
---@return cmp.AsyncThrottle
async.throttle = function(fn, timeout)
local time = nil
- local timer = vim.loop.new_timer()
+ local timer = assert(vim.loop.new_timer())
+ local _async = nil ---@type Async?
+ timers[#timers + 1] = timer
return setmetatable({
running = false,
timeout = timeout,
@@ -21,9 +40,15 @@ async.throttle = function(fn, timeout)
return not self.running
end)
end,
- stop = function()
- time = nil
+ stop = function(reset_time)
+ if reset_time ~= false then
+ time = nil
+ end
timer:stop()
+ if _async then
+ _async:cancel()
+ _async = nil
+ end
end,
}, {
__call = function(self, ...)
@@ -34,12 +59,23 @@ async.throttle = function(fn, timeout)
end
self.running = true
- timer:stop()
+ self.stop(false)
timer:start(math.max(1, self.timeout - (vim.loop.now() - time)), 0, function()
vim.schedule(function()
time = nil
- fn(unpack(args))
- self.running = false
+ local ret = fn(unpack(args))
+ if async.is_async(ret) then
+ ---@cast ret Async
+ _async = ret
+ _async:await(function(_, error)
+ self.running = false
+ if error and error ~= 'abort' then
+ vim.notify(error, vim.log.levels.ERROR)
+ end
+ end)
+ else
+ self.running = false
+ end
end)
end)
end,
@@ -60,7 +96,7 @@ end
---Timeout callback function
---@param fn function
----@param timeout number
+---@param timeout integer
---@return function
async.timeout = function(fn, timeout)
local timer
@@ -109,4 +145,146 @@ async.sync = function(runner, timeout)
end, 10, false)
end
+---Wait and callback for next safe state.
+async.debounce_next_tick = function(callback)
+ local running = false
+ return function()
+ if running then
+ return
+ end
+ running = true
+ vim.schedule(function()
+ running = false
+ callback()
+ end)
+ end
+end
+
+---Wait and callback for consuming next keymap.
+async.debounce_next_tick_by_keymap = function(callback)
+ return function()
+ feedkeys.call('', '', callback)
+ end
+end
+
+local Scheduler = {}
+Scheduler._queue = {}
+Scheduler._executor = assert(vim.loop.new_check())
+
+function Scheduler.step()
+ local budget = config.get().performance.async_budget * 1e6
+ local start = vim.loop.hrtime()
+ while #Scheduler._queue > 0 and vim.loop.hrtime() - start < budget do
+ local a = table.remove(Scheduler._queue, 1)
+ a:_step()
+ if a.running then
+ table.insert(Scheduler._queue, a)
+ end
+ end
+ if #Scheduler._queue == 0 then
+ return Scheduler._executor:stop()
+ end
+end
+
+---@param a Async
+function Scheduler.add(a)
+ table.insert(Scheduler._queue, a)
+ if not Scheduler._executor:is_active() then
+ Scheduler._executor:start(vim.schedule_wrap(Scheduler.step))
+ end
+end
+
+--- @alias AsyncCallback fun(result?:any, error?:string)
+
+--- @class Async
+--- @field running boolean
+--- @field result? any
+--- @field error? string
+--- @field callbacks AsyncCallback[]
+--- @field thread thread
+local Async = {}
+Async.__index = Async
+
+function Async.new(fn)
+ local self = setmetatable({}, Async)
+ self.callbacks = {}
+ self.running = true
+ self.thread = coroutine.create(fn)
+ Scheduler.add(self)
+ return self
+end
+
+---@param result? any
+---@param error? string
+function Async:_done(result, error)
+ self.running = false
+ self.result = result
+ self.error = error
+ for _, callback in ipairs(self.callbacks) do
+ callback(result, error)
+ end
+end
+
+function Async:_step()
+ local ok, res = coroutine.resume(self.thread)
+ if not ok then
+ return self:_done(nil, res)
+ elseif res == 'abort' then
+ return self:_done(nil, 'abort')
+ elseif coroutine.status(self.thread) == 'dead' then
+ return self:_done(res)
+ end
+end
+
+function Async:cancel()
+ self.running = false
+end
+
+---@param cb AsyncCallback
+function Async:await(cb)
+ if not cb then
+ error('callback is required')
+ end
+ if self.running then
+ table.insert(self.callbacks, cb)
+ else
+ cb(self.result, self.error)
+ end
+end
+
+function Async:sync()
+ while self.running do
+ vim.wait(10)
+ end
+ return self.error and error(self.error) or self.result
+end
+
+--- @return boolean
+function async.is_async(obj)
+ return obj and type(obj) == 'table' and getmetatable(obj) == Async
+end
+
+--- @return fun(...): Async
+function async.wrap(fn)
+ return function(...)
+ local args = { ... }
+ return Async.new(function()
+ return fn(unpack(args))
+ end)
+ end
+end
+
+-- This will yield when called from a coroutine
+function async.yield(...)
+ if not coroutine.isyieldable() then
+ error('Trying to yield from a non-yieldable context')
+ return ...
+ end
+ return coroutine.yield(...)
+end
+
+function async.abort()
+ return async.yield('abort')
+end
+
return async
diff --git a/bundle/nvim-cmp/lua/cmp/utils/autocmd.lua b/bundle/nvim-cmp/lua/cmp/utils/autocmd.lua
index 4af766a07..438e23190 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/autocmd.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/autocmd.lua
@@ -2,20 +2,38 @@ local debug = require('cmp.utils.debug')
local autocmd = {}
+autocmd.group = vim.api.nvim_create_augroup('___cmp___', { clear = true })
+
autocmd.events = {}
---Subscribe autocmd
----@param event string
+---@param events string|string[]
---@param callback function
---@return function
-autocmd.subscribe = function(event, callback)
- autocmd.events[event] = autocmd.events[event] or {}
- table.insert(autocmd.events[event], callback)
+autocmd.subscribe = function(events, callback)
+ events = type(events) == 'string' and { events } or events
+
+ for _, event in ipairs(events) do
+ if not autocmd.events[event] then
+ autocmd.events[event] = {}
+ vim.api.nvim_create_autocmd(event, {
+ desc = ('nvim-cmp: autocmd: %s'):format(event),
+ group = autocmd.group,
+ callback = function()
+ autocmd.emit(event)
+ end,
+ })
+ end
+ table.insert(autocmd.events[event], callback)
+ end
+
return function()
- for i, callback_ in ipairs(autocmd.events[event]) do
- if callback_ == callback then
- table.remove(autocmd.events[event], i)
- break
+ for _, event in ipairs(events) do
+ for i, callback_ in ipairs(autocmd.events[event]) do
+ if callback_ == callback then
+ table.remove(autocmd.events[event], i)
+ break
+ end
end
end
end
diff --git a/bundle/nvim-cmp/lua/cmp/utils/binary.lua b/bundle/nvim-cmp/lua/cmp/utils/binary.lua
index ab1b0d225..b799f969e 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/binary.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/binary.lua
@@ -3,7 +3,7 @@ local binary = {}
---Insert item to list to ordered index
---@param list any[]
---@param item any
----@param func fun(a: any, b: any): "1"|"-1"|"0"
+---@param func fun(a: any, b: any): 1|-1|0
binary.insort = function(list, item, func)
table.insert(list, binary.search(list, item, func), item)
end
@@ -11,8 +11,8 @@ end
---Search suitable index from list
---@param list any[]
---@param item any
----@param func fun(a: any, b: any): "1"|"-1"|"0"
----@return number
+---@param func fun(a: any, b: any): 1|-1|0
+---@return integer
binary.search = function(list, item, func)
local s = 1
local e = #list
diff --git a/bundle/nvim-cmp/lua/cmp/utils/buffer.lua b/bundle/nvim-cmp/lua/cmp/utils/buffer.lua
index b48a1b1ff..0705cbc74 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/buffer.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/buffer.lua
@@ -2,7 +2,7 @@ local buffer = {}
buffer.cache = {}
----@return number buf
+---@return integer buf
buffer.get = function(name)
local buf = buffer.cache[name]
if buf and vim.api.nvim_buf_is_valid(buf) then
@@ -12,7 +12,7 @@ buffer.get = function(name)
end
end
----@return number buf
+---@return integer buf
---@return boolean created_new
buffer.ensure = function(name)
local created_new = false
@@ -20,8 +20,6 @@ buffer.ensure = function(name)
if not buf then
created_new = true
buf = vim.api.nvim_create_buf(false, true)
- vim.api.nvim_buf_set_option(buf, 'buftype', 'nofile')
- vim.api.nvim_buf_set_option(buf, 'bufhidden', 'hide')
buffer.cache[name] = buf
end
return buf, created_new
diff --git a/bundle/nvim-cmp/lua/cmp/utils/cache.lua b/bundle/nvim-cmp/lua/cmp/utils/cache.lua
index 8607b2a3f..26456ad4b 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/cache.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/cache.lua
@@ -9,7 +9,7 @@ cache.new = function()
end
---Get cache value
----@param key string
+---@param key string|string[]
---@return any|nil
cache.get = function(self, key)
key = self:key(key)
@@ -20,7 +20,7 @@ cache.get = function(self, key)
end
---Set cache value explicitly
----@param key string
+---@param key string|string[]
---@vararg any
cache.set = function(self, key, value)
key = self:key(key)
@@ -28,8 +28,10 @@ cache.set = function(self, key, value)
end
---Ensure value by callback
----@param key string
----@param callback fun(): any
+---@generic T
+---@param key string|string[]
+---@param callback fun(): T
+---@return T
cache.ensure = function(self, key, callback)
local value = self:get(key)
if value == nil then
@@ -46,7 +48,7 @@ cache.clear = function(self)
end
---Create key
----@param key string|table
+---@param key string|string[]
---@return string
cache.key = function(_, key)
if type(key) == 'table' then
diff --git a/bundle/nvim-cmp/lua/cmp/utils/char.lua b/bundle/nvim-cmp/lua/cmp/utils/char.lua
index 12a1809e5..c733bef31 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/char.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/char.lua
@@ -1,69 +1,71 @@
+local _
+
local alpha = {}
-string.gsub('abcdefghijklmnopqrstuvwxyz', '.', function(char)
+_ = string.gsub('abcdefghijklmnopqrstuvwxyz', '.', function(char)
alpha[string.byte(char)] = true
end)
local ALPHA = {}
-string.gsub('ABCDEFGHIJKLMNOPQRSTUVWXYZ', '.', function(char)
+_ = string.gsub('ABCDEFGHIJKLMNOPQRSTUVWXYZ', '.', function(char)
ALPHA[string.byte(char)] = true
end)
local digit = {}
-string.gsub('1234567890', '.', function(char)
+_ = string.gsub('1234567890', '.', function(char)
digit[string.byte(char)] = true
end)
local white = {}
-string.gsub(' \t\n', '.', function(char)
+_ = string.gsub(' \t\n', '.', function(char)
white[string.byte(char)] = true
end)
local char = {}
----@param byte number
+---@param byte integer
---@return boolean
char.is_upper = function(byte)
return ALPHA[byte]
end
----@param byte number
+---@param byte integer
---@return boolean
char.is_alpha = function(byte)
return alpha[byte] or ALPHA[byte]
end
----@param byte number
+---@param byte integer
---@return boolean
char.is_digit = function(byte)
return digit[byte]
end
----@param byte number
+---@param byte integer
---@return boolean
char.is_white = function(byte)
return white[byte]
end
----@param byte number
+---@param byte integer
---@return boolean
char.is_symbol = function(byte)
return not (char.is_alnum(byte) or char.is_white(byte))
end
----@param byte number
+---@param byte integer
---@return boolean
char.is_printable = function(byte)
return string.match(string.char(byte), '^%c$') == nil
end
----@param byte number
+---@param byte integer
---@return boolean
char.is_alnum = function(byte)
return char.is_alpha(byte) or char.is_digit(byte)
end
---@param text string
----@param index number
+---@param index integer
---@return boolean
char.is_semantic_index = function(text, index)
if index <= 1 then
@@ -89,8 +91,8 @@ char.is_semantic_index = function(text, index)
end
---@param text string
----@param current_index number
----@return boolean
+---@param current_index integer
+---@return integer
char.get_next_semantic_index = function(text, current_index)
for i = current_index + 1, #text do
if char.is_semantic_index(text, i) then
@@ -101,8 +103,8 @@ char.get_next_semantic_index = function(text, current_index)
end
---Ignore case match
----@param byte1 number
----@param byte2 number
+---@param byte1 integer
+---@param byte2 integer
---@return boolean
char.match = function(byte1, byte2)
if not char.is_alpha(byte1) or not char.is_alpha(byte2) then
diff --git a/bundle/nvim-cmp/lua/cmp/utils/feedkeys.lua b/bundle/nvim-cmp/lua/cmp/utils/feedkeys.lua
index e7810a7e5..cd20f6068 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/feedkeys.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/feedkeys.lua
@@ -7,22 +7,18 @@ feedkeys.call = setmetatable({
callbacks = {},
}, {
__call = function(self, keys, mode, callback)
- if vim.fn.reg_recording() ~= '' then
- return feedkeys.call_macro(keys, mode, callback)
- end
-
local is_insert = string.match(mode, 'i') ~= nil
local is_immediate = string.match(mode, 'x') ~= nil
local queue = {}
if #keys > 0 then
- table.insert(queue, { keymap.t('set lazyredraw'), 'n' })
- table.insert(queue, { keymap.t('set textwidth=0'), 'n' })
- table.insert(queue, { keymap.t('set eventignore=all'), 'n' })
+ table.insert(queue, { keymap.t('setlocal lazyredraw'), 'n' })
+ table.insert(queue, { keymap.t('setlocal textwidth=0'), 'n' })
+ table.insert(queue, { keymap.t('setlocal backspace=2'), 'n' })
table.insert(queue, { keys, string.gsub(mode, '[itx]', ''), true })
- table.insert(queue, { keymap.t('set %slazyredraw'):format(vim.o.lazyredraw and '' or 'no'), 'n' })
- table.insert(queue, { keymap.t('set textwidth=%s'):format(vim.bo.textwidth or 0), 'n' })
- table.insert(queue, { keymap.t('set eventignore=%s'):format(vim.o.eventignore or ''), 'n' })
+ table.insert(queue, { keymap.t('setlocal %slazyredraw'):format(vim.o.lazyredraw and '' or 'no'), 'n' })
+ table.insert(queue, { keymap.t('setlocal textwidth=%s'):format(vim.bo.textwidth or 0), 'n' })
+ table.insert(queue, { keymap.t('setlocal backspace=%s'):format(vim.go.backspace or 2), 'n' })
end
if callback then
@@ -54,57 +50,4 @@ misc.set(_G, { 'cmp', 'utils', 'feedkeys', 'call', 'run' }, function(id)
return ''
end)
-feedkeys.call_macro = setmetatable({
- queue = {},
- current = nil,
- timer = vim.loop.new_timer(),
- running = false,
-}, {
- __call = function(self, keys, mode, callback)
- local is_insert = string.match(mode, 'i') ~= nil
- table.insert(self.queue, is_insert and 1 or #self.queue + 1, {
- keys = keys,
- mode = mode,
- callback = callback,
- })
-
- if not self.running then
- self.running = true
- local consume
- consume = vim.schedule_wrap(function()
- if vim.fn.getchar(1) == 0 then
- if self.current then
- vim.cmd(('set backspace=%s'):format(self.current.backspace or ''))
- vim.cmd(('set eventignore=%s'):format(self.current.eventignore or ''))
- if self.current.callback then
- self.current.callback()
- end
- self.current = nil
- end
-
- local current = table.remove(self.queue, 1)
- if current then
- self.current = {
- keys = current.keys,
- callback = current.callback,
- backspace = vim.o.backspace,
- eventignore = vim.o.eventignore,
- }
- vim.api.nvim_feedkeys(keymap.t('set backspace=start'), 'n', true)
- vim.api.nvim_feedkeys(keymap.t('set eventignore=all'), 'n', true)
- vim.api.nvim_feedkeys(current.keys, string.gsub(current.mode, '[i]', ''), true) -- 'i' flag is manually resolved.
- end
- end
-
- if #self.queue ~= 0 or self.current then
- vim.defer_fn(consume, 1)
- else
- self.running = false
- end
- end)
- vim.defer_fn(consume, 1)
- end
- end,
-})
-
return feedkeys
diff --git a/bundle/nvim-cmp/lua/cmp/utils/feedkeys_spec.lua b/bundle/nvim-cmp/lua/cmp/utils/feedkeys_spec.lua
index a4e71f364..24fba71a4 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/feedkeys_spec.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/feedkeys_spec.lua
@@ -23,6 +23,15 @@ describe('feedkeys', function()
})
end)
+ it('bacckspace', function()
+ vim.cmd([[setlocal backspace=0]])
+ feedkeys.call(keymap.t('iaiueo'), 'nx')
+ feedkeys.call(keymap.t('a'), 'nx')
+ assert.are.same(vim.api.nvim_buf_get_lines(0, 0, -1, false), {
+ 'aiu',
+ })
+ end)
+
it('testability', function()
feedkeys.call('i', 'n', function()
feedkeys.call('', 'n', function()
diff --git a/bundle/nvim-cmp/lua/cmp/utils/highlight.lua b/bundle/nvim-cmp/lua/cmp/utils/highlight.lua
index cbe25f5ce..867632a0c 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/highlight.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/highlight.lua
@@ -1,46 +1,31 @@
local highlight = {}
highlight.keys = {
- 'gui',
- 'guifg',
- 'guibg',
- 'cterm',
- 'ctermfg',
- 'ctermbg',
+ 'fg',
+ 'bg',
+ 'bold',
+ 'italic',
+ 'reverse',
+ 'standout',
+ 'underline',
+ 'undercurl',
+ 'strikethrough',
}
-highlight.inherit = function(name, source, override)
- local cmd = ('highlight default %s'):format(name)
+highlight.inherit = function(name, source, settings)
for _, key in ipairs(highlight.keys) do
- if override[key] then
- cmd = cmd .. (' %s=%s'):format(key, override[key])
- else
- local v = highlight.get(source, key)
- v = v == '' and 'NONE' or v
- cmd = cmd .. (' %s=%s'):format(key, v)
- end
- end
- vim.cmd(cmd)
-end
-
-highlight.get = function(source, key)
- if key == 'gui' or key == 'cterm' then
- local ui = {}
- for _, k in ipairs({ 'bold', 'italic', 'reverse', 'inverse', 'standout', 'underline', 'undercurl', 'strikethrough' }) do
- if vim.fn.synIDattr(vim.fn.hlID(source), k, key) == 1 then
- table.insert(ui, k)
+ if not settings[key] then
+ local v = vim.fn.synIDattr(vim.fn.hlID(source), key)
+ if key == 'fg' or key == 'bg' then
+ local n = tonumber(v, 10)
+ v = type(n) == 'number' and n or v
+ else
+ v = v == 1
end
+ settings[key] = v == '' and 'NONE' or v
end
- return table.concat(ui, ',')
- elseif key == 'guifg' then
- return vim.fn.synIDattr(vim.fn.hlID(source), 'fg#', 'gui')
- elseif key == 'guibg' then
- return vim.fn.synIDattr(vim.fn.hlID(source), 'bg#', 'gui')
- elseif key == 'ctermfg' then
- return vim.fn.synIDattr(vim.fn.hlID(source), 'fg', 'term')
- elseif key == 'ctermbg' then
- return vim.fn.synIDattr(vim.fn.hlID(source), 'bg', 'term')
end
+ vim.api.nvim_set_hl(0, name, settings)
end
return highlight
diff --git a/bundle/nvim-cmp/lua/cmp/utils/keymap.lua b/bundle/nvim-cmp/lua/cmp/utils/keymap.lua
index 5727cd096..5ab0be78e 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/keymap.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/keymap.lua
@@ -1,4 +1,5 @@
local misc = require('cmp.utils.misc')
+local buffer = require('cmp.utils.buffer')
local api = require('cmp.utils.api')
local keymap = {}
@@ -16,12 +17,16 @@ end
---@param keys string
---@return string
keymap.normalize = function(keys)
- vim.api.nvim_set_keymap('t', '(cmp.utils.keymap.normalize)', keys, {})
- for _, map in ipairs(vim.api.nvim_get_keymap('t')) do
- if keymap.equals(map.lhs, '(cmp.utils.keymap.normalize)') then
- return map.rhs
+ local normalize_buf = buffer.ensure('cmp.util.keymap.normalize')
+ vim.api.nvim_buf_set_keymap(normalize_buf, 't', keys, '(cmp.utils.keymap.normalize)', {})
+ for _, map in ipairs(vim.api.nvim_buf_get_keymap(normalize_buf, 't')) do
+ if keymap.t(map.rhs) == keymap.t('(cmp.utils.keymap.normalize)') then
+ vim.api.nvim_buf_del_keymap(normalize_buf, 't', keys)
+ return map.lhs
end
end
+ vim.api.nvim_buf_del_keymap(normalize_buf, 't', keys)
+ vim.api.nvim_buf_delete(normalize_buf, {})
return keys
end
@@ -64,7 +69,7 @@ keymap.undojoin = function()
end
---Create backspace keys.
----@param count number
+---@param count string|integer
---@return string
keymap.backspace = function(count)
if type(count) == 'string' then
@@ -78,8 +83,23 @@ keymap.backspace = function(count)
return table.concat(keys, '')
end
+---Create delete keys.
+---@param count string|integer
+---@return string
+keymap.delete = function(count)
+ if type(count) == 'string' then
+ count = vim.fn.strchars(count, true)
+ end
+ if count <= 0 then
+ return ''
+ end
+ local keys = {}
+ table.insert(keys, keymap.t(string.rep('', count)))
+ return table.concat(keys, '')
+end
+
---Update indentkeys.
----@param expr string
+---@param expr? string
---@return string
keymap.indentkeys = function(expr)
return string.format(keymap.t('set indentkeys=%s'), expr and vim.fn.escape(expr, '| \t\\') or '')
@@ -90,7 +110,7 @@ end
---@param b string
---@return boolean
keymap.equals = function(a, b)
- return keymap.t(a) == keymap.t(b)
+ return keymap.normalize(a) == keymap.normalize(b)
end
---Register keypress handler.
@@ -98,8 +118,7 @@ keymap.listen = function(mode, lhs, callback)
lhs = keymap.normalize(keymap.to_keymap(lhs))
local existing = keymap.get_map(mode, lhs)
- local id = string.match(existing.rhs, 'v:lua%.cmp%.utils%.keymap%.set_map%((%d+)%)')
- if id and keymap.set_map.callbacks[tonumber(id, 10)] then
+ if existing.desc == 'cmp.utils.keymap.set_map' then
return
end
@@ -124,8 +143,8 @@ end
keymap.fallback = function(bufnr, mode, map)
return function()
if map.expr then
- local fallback_expr = string.format('(cmp.u.k.fallback_expr:%s)', map.lhs)
- keymap.set_map(bufnr, mode, fallback_expr, function()
+ local fallback_lhs = string.format('(cmp.u.k.fallback_expr:%s)', map.lhs)
+ keymap.set_map(bufnr, mode, fallback_lhs, function()
return keymap.solve(bufnr, mode, map).keys
end, {
expr = true,
@@ -133,13 +152,14 @@ keymap.fallback = function(bufnr, mode, map)
script = map.script,
nowait = map.nowait,
silent = map.silent and mode ~= 'c',
+ replace_keycodes = map.replace_keycodes,
})
- vim.api.nvim_feedkeys(keymap.t(fallback_expr), 'im', true)
- elseif not map.callback then
+ vim.api.nvim_feedkeys(keymap.t(fallback_lhs), 'im', true)
+ elseif map.callback then
+ map.callback()
+ else
local solved = keymap.solve(bufnr, mode, map)
vim.api.nvim_feedkeys(solved.keys, solved.mode, true)
- else
- map.callback()
end
end
end
@@ -147,7 +167,14 @@ end
---Solve
keymap.solve = function(bufnr, mode, map)
local lhs = keymap.t(map.lhs)
- local rhs = map.expr and (map.callback and map.callback() or vim.api.nvim_eval(keymap.t(map.rhs))) or keymap.t(map.rhs)
+ local rhs = keymap.t(map.rhs)
+ if map.expr then
+ if map.callback then
+ rhs = map.callback()
+ else
+ rhs = vim.api.nvim_eval(keymap.t(map.rhs))
+ end
+ end
if map.noremap then
return { keys = rhs, mode = 'in' }
@@ -157,9 +184,10 @@ keymap.solve = function(bufnr, mode, map)
local recursive = string.format('0_(cmp.u.k.recursive:%s)', lhs)
keymap.set_map(bufnr, mode, recursive, lhs, {
noremap = true,
- script = map.script,
+ script = true,
nowait = map.nowait,
silent = map.silent and mode ~= 'c',
+ replace_keycodes = map.replace_keycodes,
})
return { keys = keymap.t(recursive) .. string.gsub(rhs, '^' .. vim.pesc(lhs), ''), mode = 'im' }
end
@@ -180,11 +208,13 @@ keymap.get_map = function(mode, lhs)
rhs = map.rhs or '',
expr = map.expr == 1,
callback = map.callback,
+ desc = map.desc,
noremap = map.noremap == 1,
script = map.script == 1,
silent = map.silent == 1,
nowait = map.nowait == 1,
buffer = true,
+ replace_keycodes = map.replace_keycodes == 1,
}
end
end
@@ -196,11 +226,13 @@ keymap.get_map = function(mode, lhs)
rhs = map.rhs or '',
expr = map.expr == 1,
callback = map.callback,
+ desc = map.desc,
noremap = map.noremap == 1,
script = map.script == 1,
silent = map.silent == 1,
nowait = map.nowait == 1,
buffer = false,
+ replace_keycodes = map.replace_keycodes == 1,
}
end
end
@@ -215,33 +247,27 @@ keymap.get_map = function(mode, lhs)
silent = true,
nowait = false,
buffer = false,
+ replace_keycodes = true,
}
end
---Set keymapping
-keymap.set_map = setmetatable({
- callbacks = {},
-}, {
- __call = function(self, bufnr, mode, lhs, rhs, opts)
- if type(rhs) == 'function' then
- local id = misc.id('cmp.utils.keymap.set_map')
- self.callbacks[id] = rhs
- if opts.expr then
- rhs = ('v:lua.cmp.utils.keymap.set_map(%s)'):format(id)
- else
- rhs = ('call v:lua.cmp.utils.keymap.set_map(%s)'):format(id)
- end
- end
+keymap.set_map = function(bufnr, mode, lhs, rhs, opts)
+ if type(rhs) == 'function' then
+ opts.callback = rhs
+ rhs = ''
+ end
+ opts.desc = 'cmp.utils.keymap.set_map'
- if bufnr == -1 then
- vim.api.nvim_set_keymap(mode, lhs, rhs, opts)
- else
- vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts)
- end
- end,
-})
-misc.set(_G, { 'cmp', 'utils', 'keymap', 'set_map' }, function(id)
- return keymap.set_map.callbacks[id]() or ''
-end)
+ if vim.fn.has('nvim-0.8') == 0 then
+ opts.replace_keycodes = nil
+ end
+
+ if bufnr == -1 then
+ vim.api.nvim_set_keymap(mode, lhs, rhs, opts)
+ else
+ vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts)
+ end
+end
return keymap
diff --git a/bundle/nvim-cmp/lua/cmp/utils/misc.lua b/bundle/nvim-cmp/lua/cmp/utils/misc.lua
index 8dd3529a8..fda6f4723 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/misc.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/misc.lua
@@ -29,6 +29,24 @@ misc.concat = function(list1, list2)
return new_list
end
+---Repeat values
+---@generic T
+---@param str_or_tbl T
+---@param count integer
+---@return T
+misc.rep = function(str_or_tbl, count)
+ if type(str_or_tbl) == 'string' then
+ return string.rep(str_or_tbl, count)
+ end
+ local rep = {}
+ for _ = 1, count do
+ for _, v in ipairs(str_or_tbl) do
+ table.insert(rep, v)
+ end
+ end
+ return rep
+end
+
---Return the valu is empty or not.
---@param v any
---@return boolean
@@ -56,42 +74,38 @@ misc.none = vim.NIL
---Merge two tables recursively
---@generic T
----@param v1 T
----@param v2 T
+---@param tbl1 T
+---@param tbl2 T
---@return T
-misc.merge = function(v1, v2)
- local merge1 = type(v1) == 'table' and (not vim.tbl_islist(v1) or vim.tbl_isempty(v1))
- local merge2 = type(v2) == 'table' and (not vim.tbl_islist(v2) or vim.tbl_isempty(v2))
- if merge1 and merge2 then
+misc.merge = function(tbl1, tbl2)
+ local is_dict1 = type(tbl1) == 'table' and (not vim.tbl_islist(tbl1) or vim.tbl_isempty(tbl1))
+ local is_dict2 = type(tbl2) == 'table' and (not vim.tbl_islist(tbl2) or vim.tbl_isempty(tbl2))
+ if is_dict1 and is_dict2 then
local new_tbl = {}
- for k, v in pairs(v2) do
- new_tbl[k] = misc.merge(v1[k], v)
+ for k, v in pairs(tbl2) do
+ if tbl1[k] ~= misc.none then
+ new_tbl[k] = misc.merge(tbl1[k], v)
+ end
end
- for k, v in pairs(v1) do
- if v2[k] == nil and v ~= misc.none then
- new_tbl[k] = v
+ for k, v in pairs(tbl1) do
+ if tbl2[k] == nil then
+ if v ~= misc.none then
+ new_tbl[k] = misc.merge(v, {})
+ else
+ new_tbl[k] = nil
+ end
end
end
return new_tbl
end
- if v1 == misc.none then
- return nil
- end
- if v1 == nil then
- if v2 == misc.none then
- return nil
- else
- return v2
- end
- end
- if v1 == true then
- if merge2 then
- return v2
- end
- return {}
- end
- return v1
+ if tbl1 == misc.none then
+ return nil
+ elseif tbl1 == nil then
+ return misc.merge(tbl2, {})
+ else
+ return tbl1
+ end
end
---Generate id for group name
@@ -105,22 +119,12 @@ misc.id = setmetatable({
end,
})
----Check the value is nil or not.
----@param v boolean
----@return boolean
-misc.safe = function(v)
- if v == nil or v == vim.NIL then
- return nil
- end
- return v
-end
-
---Treat 1/0 as bool value
----@param v boolean|"1"|"0"
+---@param v boolean|1|0
---@param def boolean
---@return boolean
misc.bool = function(v, def)
- if misc.safe(v) == nil then
+ if v == nil then
return def
end
return v == true or v == 1
@@ -134,7 +138,7 @@ misc.set = function(t, keys, v)
local c = t
for i = 1, #keys - 1 do
local key = keys[i]
- c[key] = misc.safe(c[key]) or {}
+ c[key] = c[key] or {}
c = c[key]
end
c[keys[#keys]] = v
@@ -166,8 +170,8 @@ end
---Safe version of vim.str_utfindex
---@param text string
----@param vimindex number|nil
----@return number
+---@param vimindex integer|nil
+---@return integer
misc.to_utfindex = function(text, vimindex)
vimindex = vimindex or #text + 1
return vim.str_utfindex(text, math.max(0, math.min(vimindex - 1, #text)))
@@ -175,8 +179,8 @@ end
---Safe version of vim.str_byteindex
---@param text string
----@param utfindex number
----@return number
+---@param utfindex integer
+---@return integer
misc.to_vimindex = function(text, utfindex)
utfindex = utfindex or #text
for i = utfindex, 1, -1 do
@@ -206,12 +210,14 @@ end
misc.redraw = setmetatable({
doing = false,
force = false,
- termcode = vim.api.nvim_replace_termcodes('', true, true, true),
+ -- We use `` to redraw the screen. (Previously, We use . it will remove the unmatches search history.)
+ incsearch_redraw_keys = ' ',
}, {
__call = function(self, force)
+ local termcode = vim.api.nvim_replace_termcodes(self.incsearch_redraw_keys, true, true, true)
if vim.tbl_contains({ '/', '?' }, vim.fn.getcmdtype()) then
if vim.o.incsearch then
- return vim.api.nvim_feedkeys(self.termcode, 'in', true)
+ return vim.api.nvim_feedkeys(termcode, 'ni', true)
end
end
diff --git a/bundle/nvim-cmp/lua/cmp/utils/misc_spec.lua b/bundle/nvim-cmp/lua/cmp/utils/misc_spec.lua
index 4e705efa1..f6871553b 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/misc_spec.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/misc_spec.lua
@@ -16,6 +16,18 @@ describe('misc', function()
})
assert.are.equal(merged.a.b, 1)
+ merged = misc.merge({
+ a = {
+ i = 1,
+ },
+ }, {
+ a = {
+ c = 2,
+ },
+ })
+ assert.are.equal(merged.a.i, 1)
+ assert.are.equal(merged.a.c, 2)
+
merged = misc.merge({
a = false,
}, {
diff --git a/bundle/nvim-cmp/lua/cmp/utils/options.lua b/bundle/nvim-cmp/lua/cmp/utils/options.lua
new file mode 100644
index 000000000..7314ad1f4
--- /dev/null
+++ b/bundle/nvim-cmp/lua/cmp/utils/options.lua
@@ -0,0 +1,25 @@
+local M = {}
+
+-- Set window option without triggering the OptionSet event
+---@param window number
+---@param name string
+---@param value any
+M.win_set_option = function(window, name, value)
+ local eventignore = vim.opt.eventignore:get()
+ vim.opt.eventignore:append('OptionSet')
+ vim.api.nvim_win_set_option(window, name, value)
+ vim.opt.eventignore = eventignore
+end
+
+-- Set buffer option without triggering the OptionSet event
+---@param buffer number
+---@param name string
+---@param value any
+M.buf_set_option = function(buffer, name, value)
+ local eventignore = vim.opt.eventignore:get()
+ vim.opt.eventignore:append('OptionSet')
+ vim.api.nvim_buf_set_option(buffer, name, value)
+ vim.opt.eventignore = eventignore
+end
+
+return M
diff --git a/bundle/nvim-cmp/lua/cmp/utils/str.lua b/bundle/nvim-cmp/lua/cmp/utils/str.lua
index 450c9916b..df5e643bd 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/str.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/str.lua
@@ -1,5 +1,4 @@
local char = require('cmp.utils.char')
-local pattern = require('cmp.utils.pattern')
local str = {}
@@ -73,23 +72,6 @@ str.remove_suffix = function(text, suffix)
return string.sub(text, 1, -#suffix - 1)
end
----strikethrough
----@param text string
----@return string
-str.strikethrough = function(text)
- local r = pattern.regex('.')
- local buffer = ''
- while text ~= '' do
- local s, e = r:match_str(text)
- if not s then
- break
- end
- buffer = buffer .. string.sub(text, s, e) .. '̶'
- text = string.sub(text, e + 1)
- end
- return buffer
-end
-
---trim
---@param text string
---@return string
@@ -117,8 +99,8 @@ end
---get_word
---@param text string
----@param stop_char number
----@param min_length number
+---@param stop_char integer
+---@param min_length integer
---@return string
str.get_word = function(text, stop_char, min_length)
min_length = min_length or 0
diff --git a/bundle/nvim-cmp/lua/cmp/utils/str_spec.lua b/bundle/nvim-cmp/lua/cmp/utils/str_spec.lua
index 541414f21..1a218559f 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/str_spec.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/str_spec.lua
@@ -12,10 +12,6 @@ describe('utils.str', function()
assert.are.equal(str.get_word('import { GetStaticProps$1 } from "next";', nil, 9), 'import { GetStaticProps')
end)
- it('strikethrough', function()
- assert.are.equal(str.strikethrough('あいうえお'), 'あ̶い̶う̶え̶お̶')
- end)
-
it('remove_suffix', function()
assert.are.equal(str.remove_suffix('log()', '$0'), 'log()')
assert.are.equal(str.remove_suffix('log()$0', '$0'), 'log()')
diff --git a/bundle/nvim-cmp/lua/cmp/utils/window.lua b/bundle/nvim-cmp/lua/cmp/utils/window.lua
index 8e2bf02b5..80fb9f59c 100644
--- a/bundle/nvim-cmp/lua/cmp/utils/window.lua
+++ b/bundle/nvim-cmp/lua/cmp/utils/window.lua
@@ -1,25 +1,26 @@
-local cache = require('cmp.utils.cache')
local misc = require('cmp.utils.misc')
+local opt = require('cmp.utils.options')
local buffer = require('cmp.utils.buffer')
local api = require('cmp.utils.api')
+local config = require('cmp.config')
---@class cmp.WindowStyle
---@field public relative string
----@field public row number
----@field public col number
----@field public width number
----@field public height number
----@field public zindex number|nil
+---@field public row integer
+---@field public col integer
+---@field public width integer|float
+---@field public height integer|float
+---@field public border string|string[]|nil
+---@field public zindex integer|nil
---@class cmp.Window
---@field public name string
----@field public win number|nil
----@field public swin1 number|nil
----@field public swin2 number|nil
+---@field public win integer|nil
+---@field public thumb_win integer|nil
+---@field public sbar_win integer|nil
---@field public style cmp.WindowStyle
---@field public opt table
---@field public buffer_opt table
----@field public cache cmp.Cache
local window = {}
---new
@@ -28,10 +29,9 @@ window.new = function()
local self = setmetatable({}, { __index = window })
self.name = misc.id('cmp.utils.window.new')
self.win = nil
- self.swin1 = nil
- self.swin2 = nil
+ self.sbar_win = nil
+ self.thumb_win = nil
self.style = {}
- self.cache = cache.new()
self.opt = {}
self.buffer_opt = {}
return self
@@ -52,7 +52,7 @@ window.option = function(self, key, value)
self.opt[key] = value
if self:visible() then
- vim.api.nvim_win_set_option(self.win, key, value)
+ opt.win_set_option(self.win, key, value)
end
end
@@ -72,30 +72,35 @@ window.buffer_option = function(self, key, value)
self.buffer_opt[key] = value
local existing_buf = buffer.get(self.name)
if existing_buf then
- vim.api.nvim_buf_set_option(existing_buf, key, value)
+ opt.buf_set_option(existing_buf, key, value)
end
end
---Set style.
---@param style cmp.WindowStyle
window.set_style = function(self, style)
- if vim.o.columns and vim.o.columns <= style.col + style.width then
- style.width = vim.o.columns - style.col - 1
- end
- if vim.o.lines and vim.o.lines <= style.row + style.height then
- style.height = vim.o.lines - style.row - 1
- end
self.style = style
+ local info = self:info()
+
+ if vim.o.lines and vim.o.lines <= info.row + info.height + 1 then
+ self.style.height = vim.o.lines - info.row - info.border_info.vert - 1
+ end
+
self.style.zindex = self.style.zindex or 1
+
+ --- GUI clients are allowed to return fractional bounds, but we need integer
+ --- bounds to open the window
+ self.style.width = math.ceil(self.style.width)
+ self.style.height = math.ceil(self.style.height)
end
---Return buffer id.
----@return number
+---@return integer
window.get_buffer = function(self)
local buf, created_new = buffer.ensure(self.name)
if created_new then
for k, v in pairs(self.buffer_opt) do
- vim.api.nvim_buf_set_option(buf, k, v)
+ opt.buf_set_option(buf, k, v)
end
end
return buf
@@ -119,7 +124,7 @@ window.open = function(self, style)
s.noautocmd = true
self.win = vim.api.nvim_open_win(self:get_buffer(), false, s)
for k, v in pairs(self.opt) do
- vim.api.nvim_win_set_option(self.win, k, v)
+ opt.win_set_option(self.win, k, v)
end
end
self:update()
@@ -127,49 +132,57 @@ end
---Update
window.update = function(self)
- if self:has_scrollbar() then
- local total = self:get_content_height()
- local info = self:info()
- local bar_height = math.ceil(info.height * (info.height / total))
- local bar_offset = math.min(info.height - bar_height, math.floor(info.height * (vim.fn.getwininfo(self.win)[1].topline / total)))
- local style1 = {}
- style1.relative = 'editor'
- style1.style = 'minimal'
- style1.width = 1
- style1.height = info.height
- style1.row = info.row
- style1.col = info.col + info.width - (info.has_scrollbar and 1 or 0)
- style1.zindex = (self.style.zindex and (self.style.zindex + 1) or 1)
- if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
- vim.api.nvim_win_set_config(self.swin1, style1)
- else
- style1.noautocmd = true
- self.swin1 = vim.api.nvim_open_win(buffer.ensure(self.name .. 'sbuf1'), false, style1)
- vim.api.nvim_win_set_option(self.swin1, 'winhighlight', 'EndOfBuffer:PmenuSbar,Normal:PmenuSbar,NormalNC:PmenuSbar,NormalFloat:PmenuSbar')
+ local info = self:info()
+ if info.scrollable then
+ -- Draw the background of the scrollbar
+
+ if not info.border_info.visible then
+ local style = {
+ relative = 'editor',
+ style = 'minimal',
+ width = 1,
+ height = self.style.height,
+ row = info.row,
+ col = info.col + info.width - info.scrollbar_offset, -- info.col was already contained the scrollbar offset.
+ zindex = (self.style.zindex and (self.style.zindex + 1) or 1),
+ }
+ if self.sbar_win and vim.api.nvim_win_is_valid(self.sbar_win) then
+ vim.api.nvim_win_set_config(self.sbar_win, style)
+ else
+ style.noautocmd = true
+ self.sbar_win = vim.api.nvim_open_win(buffer.ensure(self.name .. 'sbar_buf'), false, style)
+ opt.win_set_option(self.sbar_win, 'winhighlight', 'EndOfBuffer:PmenuSbar,NormalFloat:PmenuSbar')
+ end
end
- local style2 = {}
- style2.relative = 'editor'
- style2.style = 'minimal'
- style2.width = 1
- style2.height = bar_height
- style2.row = info.row + bar_offset
- style2.col = info.col + info.width - (info.has_scrollbar and 1 or 0)
- style2.zindex = (self.style.zindex and (self.style.zindex + 2) or 2)
- if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
- vim.api.nvim_win_set_config(self.swin2, style2)
+
+ -- Draw the scrollbar thumb
+ local thumb_height = math.floor(info.inner_height * (info.inner_height / self:get_content_height()) + 0.5)
+ local thumb_offset = math.floor(info.inner_height * (vim.fn.getwininfo(self.win)[1].topline / self:get_content_height()))
+
+ local style = {
+ relative = 'editor',
+ style = 'minimal',
+ width = 1,
+ height = math.max(1, thumb_height),
+ row = info.row + thumb_offset + (info.border_info.visible and info.border_info.top or 0),
+ col = info.col + info.width - 1, -- info.col was already added scrollbar offset.
+ zindex = (self.style.zindex and (self.style.zindex + 2) or 2),
+ }
+ if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then
+ vim.api.nvim_win_set_config(self.thumb_win, style)
else
- style2.noautocmd = true
- self.swin2 = vim.api.nvim_open_win(buffer.ensure(self.name .. 'sbuf2'), false, style2)
- vim.api.nvim_win_set_option(self.swin2, 'winhighlight', 'EndOfBuffer:PmenuThumb,Normal:PmenuThumb,NormalNC:PmenuThumb,NormalFloat:PmenuThumb')
+ style.noautocmd = true
+ self.thumb_win = vim.api.nvim_open_win(buffer.ensure(self.name .. 'thumb_buf'), false, style)
+ opt.win_set_option(self.thumb_win, 'winhighlight', 'EndOfBuffer:PmenuThumb,NormalFloat:PmenuThumb')
end
else
- if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
- vim.api.nvim_win_hide(self.swin1)
- self.swin1 = nil
+ if self.sbar_win and vim.api.nvim_win_is_valid(self.sbar_win) then
+ vim.api.nvim_win_hide(self.sbar_win)
+ self.sbar_win = nil
end
- if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
- vim.api.nvim_win_hide(self.swin2)
- self.swin2 = nil
+ if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then
+ vim.api.nvim_win_hide(self.thumb_win)
+ self.thumb_win = nil
end
end
@@ -188,13 +201,13 @@ window.close = function(self)
vim.api.nvim_win_hide(self.win)
self.win = nil
end
- if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
- vim.api.nvim_win_hide(self.swin1)
- self.swin1 = nil
+ if self.sbar_win and vim.api.nvim_win_is_valid(self.sbar_win) then
+ vim.api.nvim_win_hide(self.sbar_win)
+ self.sbar_win = nil
end
- if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
- vim.api.nvim_win_hide(self.swin2)
- self.swin2 = nil
+ if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then
+ vim.api.nvim_win_hide(self.thumb_win)
+ self.thumb_win = nil
end
end
end
@@ -204,91 +217,102 @@ window.visible = function(self)
return self.win and vim.api.nvim_win_is_valid(self.win)
end
----Return the scrollbar will shown or not.
-window.has_scrollbar = function(self)
- return (self.style.height or 0) < self:get_content_height()
-end
-
---Return win info.
window.info = function(self)
- local border_width = self:get_border_width()
- local has_scrollbar = self:has_scrollbar()
- return {
+ local border_info = self:get_border_info()
+ local scrollbar = config.get().window.completion.scrollbar
+ local info = {
row = self.style.row,
col = self.style.col,
- width = self.style.width + border_width + (has_scrollbar and 1 or 0),
- height = self.style.height,
- border_width = border_width,
- has_scrollbar = has_scrollbar,
+ width = self.style.width + border_info.left + border_info.right,
+ height = self.style.height + border_info.top + border_info.bottom,
+ inner_width = self.style.width,
+ inner_height = self.style.height,
+ border_info = border_info,
+ scrollable = false,
+ scrollbar_offset = 0,
}
+
+ if self:get_content_height() > info.inner_height and scrollbar then
+ info.scrollable = true
+ if not border_info.visible then
+ info.scrollbar_offset = 1
+ info.width = info.width + 1
+ end
+ end
+
+ return info
end
----Get border width
----@return number
-window.get_border_width = function(self)
+---Return border information.
+---@return { top: integer, left: integer, right: integer, bottom: integer, vert: integer, horiz: integer, visible: boolean }
+window.get_border_info = function(self)
local border = self.style.border
- if type(border) == 'table' then
- local new_border = {}
- while #new_border < 8 do
- for _, b in ipairs(border) do
- table.insert(new_border, b)
- end
+ if not border or border == 'none' then
+ return {
+ top = 0,
+ left = 0,
+ right = 0,
+ bottom = 0,
+ vert = 0,
+ horiz = 0,
+ visible = false,
+ }
+ end
+ if type(border) == 'string' then
+ if border == 'shadow' then
+ return {
+ top = 0,
+ left = 0,
+ right = 1,
+ bottom = 1,
+ vert = 1,
+ horiz = 1,
+ visible = false,
+ }
end
- border = new_border
+ return {
+ top = 1,
+ left = 1,
+ right = 1,
+ bottom = 1,
+ vert = 2,
+ horiz = 2,
+ visible = true,
+ }
end
- local w = 0
- if border then
- if type(border) == 'string' then
- if border == 'single' then
- w = 2
- elseif border == 'solid' then
- w = 2
- elseif border == 'double' then
- w = 2
- elseif border == 'rounded' then
- w = 2
- elseif border == 'shadow' then
- w = 1
- end
- elseif type(border) == 'table' then
- local b4 = type(border[4]) == 'table' and border[4][1] or border[4]
- if #b4 > 0 then
- w = w + 1
- end
- local b8 = type(border[8]) == 'table' and border[8][1] or border[8]
- if #b8 > 0 then
- w = w + 1
- end
+ local new_border = {}
+ while #new_border <= 8 do
+ for _, b in ipairs(border) do
+ table.insert(new_border, type(b) == 'string' and b or b[1])
end
end
- return w
+ local info = {}
+ info.top = new_border[2] == '' and 0 or 1
+ info.right = new_border[4] == '' and 0 or 1
+ info.bottom = new_border[6] == '' and 0 or 1
+ info.left = new_border[8] == '' and 0 or 1
+ info.vert = info.top + info.bottom
+ info.horiz = info.left + info.right
+ info.visible = not (vim.tbl_contains({ '', ' ' }, new_border[2]) and vim.tbl_contains({ '', ' ' }, new_border[4]) and vim.tbl_contains({ '', ' ' }, new_border[6]) and vim.tbl_contains({ '', ' ' }, new_border[8]))
+ return info
end
---Get scroll height.
----@return number
+---NOTE: The result of vim.fn.strdisplaywidth depends on the buffer it was called in (see comment in cmp.Entry.get_view).
+---@return integer
window.get_content_height = function(self)
if not self:option('wrap') then
return vim.api.nvim_buf_line_count(self:get_buffer())
end
-
- return self.cache:ensure({
- 'get_content_height',
- self.style.width,
- self:get_buffer(),
- vim.api.nvim_buf_get_changedtick(self:get_buffer()),
- }, function()
- local height = 0
- local buf = self:get_buffer()
- -- The result of vim.fn.strdisplaywidth depends on the buffer it was called
- -- in (see comment in cmp.Entry.get_view).
- vim.api.nvim_buf_call(buf, function()
- for _, text in ipairs(vim.api.nvim_buf_get_lines(buf, 0, -1, false)) do
- height = height + math.ceil(math.max(1, vim.fn.strdisplaywidth(text)) / self.style.width)
- end
- end)
- return height
+ local height = 0
+ vim.api.nvim_buf_call(self:get_buffer(), function()
+ for _, text in ipairs(vim.api.nvim_buf_get_lines(self:get_buffer(), 0, -1, false)) do
+ height = height + math.max(1, math.ceil(vim.fn.strdisplaywidth(text) / self.style.width))
+ end
end)
+ return height
end
return window
diff --git a/bundle/nvim-cmp/lua/cmp/view.lua b/bundle/nvim-cmp/lua/cmp/view.lua
index 981378b9d..ef7914254 100644
--- a/bundle/nvim-cmp/lua/cmp/view.lua
+++ b/bundle/nvim-cmp/lua/cmp/view.lua
@@ -47,6 +47,7 @@ end
---Open menu
---@param ctx cmp.Context
---@param sources cmp.Source[]
+---@return boolean did_open
view.open = function(self, ctx, sources)
local source_group_map = {}
for _, s in ipairs(sources) do
@@ -104,10 +105,15 @@ view.open = function(self, ctx, sources)
end
end
end)
+ local max_item_count = config.get().performance.max_view_entries or 200
+ entries = vim.list_slice(entries, 1, max_item_count)
-- open
if #entries > 0 then
self:_get_entries_view():open(offset, entries)
+ self.event:emit('menu_opened', {
+ window = self:_get_entries_view(),
+ })
break
end
end
@@ -116,6 +122,7 @@ view.open = function(self, ctx, sources)
if #entries == 0 then
self:close()
end
+ return #entries > 0
end
---Close menu
@@ -128,6 +135,9 @@ view.close = function(self)
self:_get_entries_view():close()
self.docs_view:close()
self.ghost_text_view:hide()
+ self.event:emit('menu_closed', {
+ window = self:_get_entries_view(),
+ })
end
---Abort menu
@@ -135,6 +145,9 @@ view.abort = function(self)
self:_get_entries_view():abort()
self.docs_view:close()
self.ghost_text_view:hide()
+ self.event:emit('menu_closed', {
+ window = self:_get_entries_view(),
+ })
end
---Return the view is visible or not.
@@ -144,7 +157,7 @@ view.visible = function(self)
end
---Scroll documentation window if possible.
----@param delta number
+---@param delta integer
view.scroll_docs = function(self, delta)
self.docs_view:scroll(delta)
end
diff --git a/bundle/nvim-cmp/lua/cmp/view/custom_entries_view.lua b/bundle/nvim-cmp/lua/cmp/view/custom_entries_view.lua
index 00044801b..a5eca58b1 100644
--- a/bundle/nvim-cmp/lua/cmp/view/custom_entries_view.lua
+++ b/bundle/nvim-cmp/lua/cmp/view/custom_entries_view.lua
@@ -8,13 +8,11 @@ local keymap = require('cmp.utils.keymap')
local misc = require('cmp.utils.misc')
local api = require('cmp.utils.api')
-local SIDE_PADDING = 1
-
local DEFAULT_HEIGHT = 10 -- @see https://github.com/vim/vim/blob/master/src/popupmenu.c#L45
---@class cmp.CustomEntriesView
---@field private entries_win cmp.Window
----@field private offset number
+---@field private offset integer
---@field private active boolean
---@field private entries cmp.Entry[]
---@field private column_width any
@@ -25,20 +23,21 @@ custom_entries_view.ns = vim.api.nvim_create_namespace('cmp.view.custom_entries_
custom_entries_view.new = function()
local self = setmetatable({}, { __index = custom_entries_view })
+
self.entries_win = window.new()
self.entries_win:option('conceallevel', 2)
self.entries_win:option('concealcursor', 'n')
self.entries_win:option('cursorlineopt', 'line')
self.entries_win:option('foldenable', false)
self.entries_win:option('wrap', false)
- self.entries_win:option('scrolloff', 0)
- self.entries_win:option('winhighlight', 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None')
-- This is done so that strdisplaywidth calculations for lines in the
-- custom_entries_view window exactly match with what is really displayed,
-- see comment in cmp.Entry.get_view. Setting tabstop to 1 makes all tabs be
-- always rendered one column wide, which removes the unpredictability coming
-- from variable width of the tab character.
self.entries_win:buffer_option('tabstop', 1)
+ self.entries_win:buffer_option('filetype', 'cmp_menu')
+ self.entries_win:buffer_option('buftype', 'nofile')
self.event = event.new()
self.offset = -1
self.active = false
@@ -65,7 +64,7 @@ custom_entries_view.new = function()
local e = self.entries[i + 1]
if e then
local v = e:get_view(self.offset, buf)
- local o = SIDE_PADDING
+ local o = config.get().window.completion.side_padding
local a = 0
for _, field in ipairs(fields) do
if field == types.cmp.ItemField.Abbr then
@@ -118,17 +117,15 @@ custom_entries_view.is_direction_top_down = function(self)
end
custom_entries_view.open = function(self, offset, entries)
+ local completion = config.get().window.completion
self.offset = offset
self.entries = {}
self.column_width = { abbr = 0, kind = 0, menu = 0 }
- -- Apply window options (that might be changed) on the custom completion menu.
- self.entries_win:option('winblend', vim.o.pumblend)
-
local entries_buf = self.entries_win:get_buffer()
local lines = {}
local dedup = {}
- local preselect = 0
+ local preselect_index = 0
for _, e in ipairs(entries) do
local view = e:get_view(offset, entries_buf)
if view.dup == 1 or not dedup[e.completion_item.label] then
@@ -138,8 +135,8 @@ custom_entries_view.open = function(self, offset, entries)
self.column_width.menu = math.max(self.column_width.menu, view.menu.width)
table.insert(self.entries, e)
table.insert(lines, ' ')
- if preselect == 0 and e.completion_item.preselect then
- preselect = #self.entries
+ if preselect_index == 0 and e.completion_item.preselect then
+ preselect_index = #self.entries
end
end
end
@@ -157,18 +154,26 @@ custom_entries_view.open = function(self, offset, entries)
height = math.min(height, #self.entries)
local pos = api.get_screen_cursor()
- local cursor = api.get_cursor()
- local delta = cursor[2] + 1 - self.offset
- local has_bottom_space = (vim.o.lines - pos[1]) >= DEFAULT_HEIGHT
+ local cursor_before_line = api.get_cursor_before_line()
+ local delta = vim.fn.strdisplaywidth(cursor_before_line:sub(self.offset))
local row, col = pos[1], pos[2] - delta - 1
- if not has_bottom_space and math.floor(vim.o.lines * 0.5) <= row and vim.o.lines - row <= height then
+ local border_info = window.get_border_info({ style = completion })
+ local border_offset_row = border_info.top + border_info.bottom
+ local border_offset_col = border_info.left + border_info.right
+ if math.floor(vim.o.lines * 0.5) <= row + border_offset_row and vim.o.lines - row - border_offset_row <= math.min(DEFAULT_HEIGHT, height) then
height = math.min(height, row - 1)
- row = row - height - 1
+ row = row - height - border_offset_row - 1
+ if row < 0 then
+ height = height + row
+ end
end
- if math.floor(vim.o.columns * 0.5) <= col and vim.o.columns - col <= width then
+ if math.floor(vim.o.columns * 0.5) <= col + border_offset_col and vim.o.columns - col - border_offset_col <= width then
width = math.min(width, vim.o.columns - 1)
- col = vim.o.columns - width - 1
+ col = vim.o.columns - width - border_offset_col - 1
+ if col < 0 then
+ width = width + col
+ end
end
if pos[1] > row then
@@ -182,35 +187,40 @@ custom_entries_view.open = function(self, offset, entries)
for i = 1, math.floor(n / 2) do
self.entries[i], self.entries[n - i + 1] = self.entries[n - i + 1], self.entries[i]
end
- if preselect ~= 0 then
- preselect = #self.entries - preselect + 1
+ if preselect_index ~= 0 then
+ preselect_index = #self.entries - preselect_index + 1
end
end
+ -- Apply window options (that might be changed) on the custom completion menu.
+ self.entries_win:option('winblend', vim.o.pumblend)
+ self.entries_win:option('winhighlight', completion.winhighlight)
+ self.entries_win:option('scrolloff', completion.scrolloff)
self.entries_win:open({
relative = 'editor',
style = 'minimal',
row = math.max(0, row),
- col = math.max(0, col),
+ col = math.max(0, col + completion.col_offset),
width = width,
height = height,
- zindex = 1001,
+ border = completion.border,
+ zindex = completion.zindex or 1001,
})
-- always set cursor when starting. It will be adjusted on the call to _select
vim.api.nvim_win_set_cursor(self.entries_win.win, { 1, 0 })
- if preselect > 0 and config.get().preselect == types.cmp.PreselectMode.Item then
- self:_select(preselect, { behavior = types.cmp.SelectBehavior.Select })
+ if preselect_index > 0 and config.get().preselect == types.cmp.PreselectMode.Item then
+ self:_select(preselect_index, { behavior = types.cmp.SelectBehavior.Select, active = false })
elseif not string.match(config.get().completion.completeopt, 'noselect') then
if self:is_direction_top_down() then
- self:_select(1, { behavior = types.cmp.SelectBehavior.Select })
+ self:_select(1, { behavior = types.cmp.SelectBehavior.Select, active = false })
else
- self:_select(#self.entries - 1, { behavior = types.cmp.SelectBehavior.Select })
+ self:_select(#self.entries, { behavior = types.cmp.SelectBehavior.Select, active = false })
end
else
if self:is_direction_top_down() then
- self:_select(0, { behavior = types.cmp.SelectBehavior.Select })
+ self:_select(0, { behavior = types.cmp.SelectBehavior.Select, active = false })
else
- self:_select(#self.entries + 1, { behavior = types.cmp.SelectBehavior.Select })
+ self:_select(#self.entries + 1, { behavior = types.cmp.SelectBehavior.Select, active = false })
end
end
end
@@ -245,12 +255,12 @@ custom_entries_view.draw = function(self)
if e then
local view = e:get_view(self.offset, entries_buf)
local text = {}
- table.insert(text, string.rep(' ', SIDE_PADDING))
+ table.insert(text, string.rep(' ', config.get().window.completion.side_padding))
for _, field in ipairs(fields) do
table.insert(text, view[field].text)
table.insert(text, string.rep(' ', 1 + self.column_width[field] - view[field].width))
end
- table.insert(text, string.rep(' ', SIDE_PADDING))
+ table.insert(text, string.rep(' ', config.get().window.completion.side_padding))
table.insert(texts, table.concat(text, ''))
end
end
@@ -275,34 +285,74 @@ end
custom_entries_view.select_next_item = function(self, option)
if self:visible() then
local cursor = vim.api.nvim_win_get_cursor(self.entries_win.win)[1]
- if self:is_direction_top_down() then
- cursor = cursor + 1
- else
- cursor = cursor - 1
- end
+ local is_top_down = self:is_direction_top_down()
+ local last = #self.entries
+
if not self.entries_win:option('cursorline') then
- cursor = (self:is_direction_top_down() and 1) or #self.entries
- elseif #self.entries < cursor then
- cursor = (not self:is_direction_top_down() and #self.entries + 1) or 0
+ cursor = (is_top_down and 1) or last
+ else
+ if is_top_down then
+ if cursor == last then
+ cursor = 0
+ else
+ cursor = cursor + option.count
+ if last < cursor then
+ cursor = last
+ end
+ end
+ else
+ if cursor == 0 then
+ cursor = last
+ else
+ cursor = cursor - option.count
+ if cursor < 0 then
+ cursor = 0
+ end
+ end
+ end
end
- self:_select(cursor, option)
+
+ self:_select(cursor, {
+ behavior = option.behavior or types.cmp.SelectBehavior.Insert,
+ active = true,
+ })
end
end
custom_entries_view.select_prev_item = function(self, option)
if self:visible() then
local cursor = vim.api.nvim_win_get_cursor(self.entries_win.win)[1]
- if self:is_direction_top_down() then
- cursor = cursor - 1
- else
- cursor = cursor + 1
- end
+ local is_top_down = self:is_direction_top_down()
+ local last = #self.entries
+
if not self.entries_win:option('cursorline') then
- cursor = (self:is_direction_top_down() and #self.entries) or 1
- elseif #self.entries < cursor then
- cursor = (not self:is_direction_top_down() and 0) or #self.entries + 1
+ cursor = (is_top_down and last) or 1
+ else
+ if is_top_down then
+ if cursor == 1 then
+ cursor = 0
+ else
+ cursor = cursor - option.count
+ if cursor < 0 then
+ cursor = 1
+ end
+ end
+ else
+ if cursor == last then
+ cursor = 0
+ else
+ cursor = cursor + option.count
+ if last < cursor then
+ cursor = last
+ end
+ end
+ end
end
- self:_select(cursor, option)
+
+ self:_select(cursor, {
+ behavior = option.behavior or types.cmp.SelectBehavior.Insert,
+ active = true,
+ })
end
end
@@ -343,10 +393,9 @@ custom_entries_view._select = function(self, cursor, option)
if is_insert and not self.active then
self.prefix = string.sub(api.get_current_line(), self.offset, api.get_cursor()[2]) or ''
end
+ self.active = (0 < cursor and cursor <= #self.entries and option.active == true)
- self.active = cursor > 0 and cursor <= #self.entries and is_insert
self.entries_win:option('cursorline', cursor > 0 and cursor <= #self.entries)
-
vim.api.nvim_win_set_cursor(self.entries_win.win, {
math.max(math.min(cursor, #self.entries), 1),
0,
@@ -368,7 +417,17 @@ custom_entries_view._insert = setmetatable({
word = word or ''
if api.is_cmdline_mode() then
local cursor = api.get_cursor()
- vim.api.nvim_feedkeys(keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])) .. word, 'int', true)
+ -- setcmdline() added in v0.8.0
+ if vim.fn.has('nvim-0.8') == 1 then
+ local current_line = api.get_current_line()
+ local before_line = current_line:sub(1, self.offset - 1)
+ local after_line = current_line:sub(cursor[2] + 1)
+ local pos = #before_line + #word + 1
+ vim.fn.setcmdline(before_line .. word .. after_line, pos)
+ vim.api.nvim_feedkeys(keymap.t('redraw'), 'ni', false)
+ else
+ vim.api.nvim_feedkeys(keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])) .. word, 'int', true)
+ end
else
if this.pending then
return
diff --git a/bundle/nvim-cmp/lua/cmp/view/docs_view.lua b/bundle/nvim-cmp/lua/cmp/view/docs_view.lua
index 5a9acb4b7..3947e33f8 100644
--- a/bundle/nvim-cmp/lua/cmp/view/docs_view.lua
+++ b/bundle/nvim-cmp/lua/cmp/view/docs_view.lua
@@ -15,7 +15,10 @@ docs_view.new = function()
self.window:option('foldenable', false)
self.window:option('linebreak', true)
self.window:option('scrolloff', 0)
+ self.window:option('showbreak', 'NONE')
self.window:option('wrap', true)
+ self.window:buffer_option('filetype', 'cmp_docs')
+ self.window:buffer_option('buftype', 'nofile')
return self
end
@@ -23,7 +26,7 @@ end
---@param e cmp.Entry
---@param view cmp.WindowStyle
docs_view.open = function(self, e, view)
- local documentation = config.get().documentation
+ local documentation = config.get().window.documentation
if not documentation then
return
end
@@ -32,11 +35,12 @@ docs_view.open = function(self, e, view)
return self:close()
end
- local right_space = vim.o.columns - (view.col + view.width) - 2
- local left_space = view.col - 2
- local maxwidth = math.min(documentation.maxwidth, math.max(left_space, right_space) - 1)
+ local border_info = window.get_border_info({ style = documentation })
+ local right_space = vim.o.columns - (view.col + view.width) - 1
+ local left_space = view.col - 1
+ local max_width = math.min(documentation.max_width, math.max(left_space, right_space))
- -- update buffer content if needed.
+ -- Update buffer content if needed.
if not self.entry or e.id ~= self.entry.id then
local documents = e:get_documentation()
if #documents == 0 then
@@ -46,24 +50,29 @@ docs_view.open = function(self, e, view)
self.entry = e
vim.api.nvim_buf_call(self.window:get_buffer(), function()
vim.cmd([[syntax clear]])
+ vim.api.nvim_buf_set_lines(self.window:get_buffer(), 0, -1, false, {})
end)
vim.lsp.util.stylize_markdown(self.window:get_buffer(), documents, {
- max_width = maxwidth,
- max_height = documentation.maxheight,
+ max_width = max_width - border_info.horiz,
+ max_height = documentation.max_height,
})
end
+ -- Set buffer as not modified, so it can be removed without errors
+ vim.api.nvim_buf_set_option(self.window:get_buffer(), 'modified', false)
+
+ -- Calculate window size.
local width, height = vim.lsp.util._make_floating_popup_size(vim.api.nvim_buf_get_lines(self.window:get_buffer(), 0, -1, false), {
- max_width = maxwidth,
- max_height = documentation.maxheight,
+ max_width = max_width - border_info.horiz,
+ max_height = documentation.max_height - border_info.vert,
})
if width <= 0 or height <= 0 then
return self:close()
end
+ -- Calculate window position.
local right_col = view.col + view.width
- local left_col = view.col - width - 2
-
+ local left_col = view.col - width - border_info.horiz
local col, left
if right_space >= width and left_space >= width then
if right_space < left_space then
@@ -81,8 +90,10 @@ docs_view.open = function(self, e, view)
return self:close()
end
+ -- Render window.
+ self.window:option('winblend', vim.o.pumblend)
self.window:option('winhighlight', documentation.winhighlight)
- self.window:set_style({
+ local style = {
relative = 'editor',
style = 'minimal',
width = width,
@@ -91,11 +102,14 @@ docs_view.open = function(self, e, view)
col = col,
border = documentation.border,
zindex = documentation.zindex or 50,
- })
- if left and self.window:has_scrollbar() then
- self.window.style.col = self.window.style.col - 1
+ }
+ self.window:open(style)
+
+ -- Correct left-col for scrollbar existence.
+ if left then
+ style.col = style.col - self.window:info().scrollbar_offset
+ self.window:open(style)
end
- self.window:open()
end
---Close floating window
diff --git a/bundle/nvim-cmp/lua/cmp/view/ghost_text_view.lua b/bundle/nvim-cmp/lua/cmp/view/ghost_text_view.lua
index b798ebb38..0b3be5cd6 100644
--- a/bundle/nvim-cmp/lua/cmp/view/ghost_text_view.lua
+++ b/bundle/nvim-cmp/lua/cmp/view/ghost_text_view.lua
@@ -9,6 +9,18 @@ local ghost_text_view = {}
ghost_text_view.ns = vim.api.nvim_create_namespace('cmp:GHOST_TEXT')
+local has_inline = (function()
+ return (pcall(function()
+ local id = vim.api.nvim_buf_set_extmark(0, ghost_text_view.ns, 0, 0, {
+ virt_text = { { ' ', 'Comment' } },
+ virt_text_pos = 'inline',
+ hl_mode = 'combine',
+ ephemeral = true,
+ })
+ vim.api.nvim_buf_del_extmark(0, ghost_text_view.ns, id)
+ end))
+end)()
+
ghost_text_view.new = function()
local self = setmetatable({}, { __index = ghost_text_view })
self.win = nil
@@ -17,7 +29,7 @@ ghost_text_view.new = function()
on_win = function(_, win)
return win == self.win
end,
- on_line = function(_)
+ on_line = function(_, _, _, on_row)
local c = config.get().experimental.ghost_text
if not c then
return
@@ -28,17 +40,23 @@ ghost_text_view.new = function()
end
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
- local line = vim.api.nvim_get_current_line()
- if string.sub(line, col + 1) ~= '' then
+ if on_row ~= row - 1 then
return
end
+ local line = vim.api.nvim_get_current_line()
+ if not has_inline then
+ if string.sub(line, col + 1) ~= '' then
+ return
+ end
+ end
+
local text = self.text_gen(self, line, col)
if #text > 0 then
vim.api.nvim_buf_set_extmark(0, ghost_text_view.ns, row - 1, col, {
right_gravity = false,
- virt_text = { { text, c.hl_group or 'Comment' } },
- virt_text_pos = 'overlay',
+ virt_text = { { text, type(c) == 'table' and c.hl_group or 'Comment' } },
+ virt_text_pos = has_inline and 'inline' or 'overlay',
hl_mode = 'combine',
ephemeral = true,
})
@@ -78,6 +96,10 @@ ghost_text_view.show = function(self, e)
if not api.is_insert_mode() then
return
end
+ local c = config.get().experimental.ghost_text
+ if not c then
+ return
+ end
local changed = e ~= self.entry
self.win = vim.api.nvim_get_current_win()
self.entry = e
diff --git a/bundle/nvim-cmp/lua/cmp/view/native_entries_view.lua b/bundle/nvim-cmp/lua/cmp/view/native_entries_view.lua
index d1dc8de7a..35ad4f2a5 100644
--- a/bundle/nvim-cmp/lua/cmp/view/native_entries_view.lua
+++ b/bundle/nvim-cmp/lua/cmp/view/native_entries_view.lua
@@ -7,10 +7,10 @@ local config = require('cmp.config')
local api = require('cmp.utils.api')
---@class cmp.NativeEntriesView
----@field private offset number
+---@field private offset integer
---@field private items vim.CompletedItem
---@field private entries cmp.Entry[]
----@field private preselect_index number
+---@field private preselect_index integer
---@field public event cmp.Event
local native_entries_view = {}
@@ -77,8 +77,7 @@ native_entries_view.open = function(self, offset, entries)
end
native_entries_view.close = function(self)
- if api.is_suitable_mode() and self:visible() then
- vim.fn.complete(1, {})
+ if api.is_insert_mode() and self:visible() then
vim.api.nvim_select_popupmenu_item(-1, false, true, {})
end
self.offset = -1
@@ -101,10 +100,10 @@ native_entries_view.info = function(self)
if self:visible() then
local info = vim.fn.pum_getpos()
return {
- width = info.width + (info.scrollbar and 1 or 0),
+ width = info.width + (info.scrollbar and 1 or 0) + (info.col == 0 and 0 or 1),
height = info.height,
row = info.row,
- col = info.col,
+ col = info.col == 0 and 0 or info.col - 1,
}
end
end
@@ -123,9 +122,9 @@ native_entries_view.select_next_item = function(self, option)
end
if self:visible() then
if (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert then
- feedkeys.call(keymap.t(''), 'n', callback)
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'n', callback)
else
- feedkeys.call(keymap.t(''), 'n', callback)
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'n', callback)
end
end
end
@@ -136,9 +135,9 @@ native_entries_view.select_prev_item = function(self, option)
end
if self:visible() then
if (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert then
- feedkeys.call(keymap.t(''), 'n', callback)
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'n', callback)
else
- feedkeys.call(keymap.t(''), 'n', callback)
+ feedkeys.call(keymap.t(string.rep('', option.count)), 'n', callback)
end
end
end
diff --git a/bundle/nvim-cmp/lua/cmp/view/wildmenu_entries_view.lua b/bundle/nvim-cmp/lua/cmp/view/wildmenu_entries_view.lua
index 3419164f4..e5608d6d8 100644
--- a/bundle/nvim-cmp/lua/cmp/view/wildmenu_entries_view.lua
+++ b/bundle/nvim-cmp/lua/cmp/view/wildmenu_entries_view.lua
@@ -9,7 +9,7 @@ local misc = require('cmp.utils.misc')
local api = require('cmp.utils.api')
---@class cmp.CustomEntriesView
----@field private offset number
+---@field private offset integer
---@field private entries_win cmp.Window
---@field private active boolean
---@field private entries cmp.Entry[]
@@ -181,11 +181,14 @@ end
wildmenu_entries_view.select_next_item = function(self, option)
if self:visible() then
+ local cursor
if self.selected_index == 0 or self.selected_index == #self.entries then
- self:_select(1, option)
+ cursor = option.count
else
- self:_select(self.selected_index + 1, option)
+ cursor = self.selected_index + option.count
end
+ cursor = math.max(math.min(cursor, #self.entries), 0)
+ self:_select(cursor, option)
end
end
@@ -194,7 +197,7 @@ wildmenu_entries_view.select_prev_item = function(self, option)
if self.selected_index == 0 or self.selected_index <= 1 then
self:_select(#self.entries, option)
else
- self:_select(self.selected_index - 1, option)
+ self:_select(math.max(self.selected_index - option.count, 0), option)
end
end
end
diff --git a/bundle/nvim-cmp/lua/cmp/vim_source.lua b/bundle/nvim-cmp/lua/cmp/vim_source.lua
index 2ee8fbf37..5af9a72a3 100644
--- a/bundle/nvim-cmp/lua/cmp/vim_source.lua
+++ b/bundle/nvim-cmp/lua/cmp/vim_source.lua
@@ -2,7 +2,7 @@ local misc = require('cmp.utils.misc')
local vim_source = {}
----@param id number
+---@param id integer
---@param args any[]
vim_source.on_callback = function(id, args)
if vim_source.to_callback.callbacks[id] then
@@ -11,7 +11,7 @@ vim_source.on_callback = function(id, args)
end
---@param callback function
----@return number
+---@return integer
vim_source.to_callback = setmetatable({
callbacks = {},
}, {
@@ -36,7 +36,7 @@ vim_source.to_args = function(args)
return args
end
----@param bridge_id number
+---@param bridge_id integer
---@param methods string[]
vim_source.new = function(bridge_id, methods)
local self = {}
diff --git a/bundle/nvim-cmp/nvim-cmp-scm-1.rockspec b/bundle/nvim-cmp/nvim-cmp-scm-1.rockspec
new file mode 100644
index 000000000..3b6e93ea9
--- /dev/null
+++ b/bundle/nvim-cmp/nvim-cmp-scm-1.rockspec
@@ -0,0 +1,31 @@
+local MODREV, SPECREV = 'scm', '-1'
+rockspec_format = '3.0'
+package = 'nvim-cmp'
+version = MODREV .. SPECREV
+
+description = {
+ summary = 'A completion plugin for neovim',
+ labels = { 'neovim' },
+ detailed = [[
+ A completion engine plugin for neovim written in Lua. Completion sources are installed from external repositories and "sourced".
+ ]],
+ homepage = 'https://github.com/hrsh7th/nvim-cmp',
+ license = 'MIT',
+}
+
+dependencies = {
+ 'lua >= 5.1, < 5.4',
+}
+
+source = {
+ url = 'git://github.com/hrsh7th/nvim-cmp',
+}
+
+build = {
+ type = 'builtin',
+ copy_directories = {
+ 'autoload',
+ 'plugin',
+ 'doc'
+ }
+}
diff --git a/bundle/nvim-cmp/plugin/cmp.lua b/bundle/nvim-cmp/plugin/cmp.lua
index a977a78a7..eaacad83f 100644
--- a/bundle/nvim-cmp/plugin/cmp.lua
+++ b/bundle/nvim-cmp/plugin/cmp.lua
@@ -3,140 +3,57 @@ if vim.g.loaded_cmp then
end
vim.g.loaded_cmp = true
-local api = require "cmp.utils.api"
-local misc = require('cmp.utils.misc')
+if not vim.api.nvim_create_autocmd then
+ return print('[nvim-cmp] Your nvim does not has `nvim_create_autocmd` function. Please update to latest nvim.')
+end
+
+local api = require('cmp.utils.api')
local types = require('cmp.types')
-local config = require('cmp.config')
local highlight = require('cmp.utils.highlight')
+local autocmd = require('cmp.utils.autocmd')
--- TODO: https://github.com/neovim/neovim/pull/14661
-vim.cmd [[
- augroup ___cmp___
- autocmd!
- autocmd InsertEnter * lua require'cmp.utils.autocmd'.emit('InsertEnter')
- autocmd InsertLeave * lua require'cmp.utils.autocmd'.emit('InsertLeave')
- autocmd TextChangedI,TextChangedP * lua require'cmp.utils.autocmd'.emit('TextChanged')
- autocmd CursorMovedI * lua require'cmp.utils.autocmd'.emit('CursorMoved')
- autocmd CompleteChanged * lua require'cmp.utils.autocmd'.emit('CompleteChanged')
- autocmd CompleteDone * lua require'cmp.utils.autocmd'.emit('CompleteDone')
- autocmd ColorScheme * call v:lua.cmp.plugin.colorscheme()
- autocmd CmdlineEnter * call v:lua.cmp.plugin.cmdline.enter()
- autocmd CmdwinEnter * call v:lua.cmp.plugin.cmdline.leave() " for entering cmdwin with ``
- augroup END
-]]
-
-misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'enter' }, function()
- if config.is_native_menu() then
- return
+vim.api.nvim_set_hl(0, 'CmpItemAbbr', { link = 'CmpItemAbbrDefault', default = true })
+vim.api.nvim_set_hl(0, 'CmpItemAbbrDeprecated', { link = 'CmpItemAbbrDeprecatedDefault', default = true })
+vim.api.nvim_set_hl(0, 'CmpItemAbbrMatch', { link = 'CmpItemAbbrMatchDefault', default = true })
+vim.api.nvim_set_hl(0, 'CmpItemAbbrMatchFuzzy', { link = 'CmpItemAbbrMatchFuzzyDefault', default = true })
+vim.api.nvim_set_hl(0, 'CmpItemKind', { link = 'CmpItemKindDefault', default = true })
+vim.api.nvim_set_hl(0, 'CmpItemMenu', { link = 'CmpItemMenuDefault', default = true })
+for kind in pairs(types.lsp.CompletionItemKind) do
+ if type(kind) == 'string' then
+ local name = ('CmpItemKind%s'):format(kind)
+ vim.api.nvim_set_hl(0, name, { link = ('%sDefault'):format(name), default = true })
end
- if vim.fn.expand('')~= '=' then
- vim.schedule(function()
- if api.is_cmdline_mode() then
- vim.cmd [[
- augroup cmp-cmdline
- autocmd!
- autocmd CmdlineChanged * lua require'cmp.utils.autocmd'.emit('TextChanged')
- autocmd CmdlineLeave * call v:lua.cmp.plugin.cmdline.leave()
- augroup END
- ]]
- require('cmp.utils.autocmd').emit('CmdlineEnter')
- end
- end)
- end
-end)
+end
-misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'leave' }, function()
- if vim.fn.expand('') ~= '=' then
- vim.cmd [[
- augroup cmp-cmdline
- autocmd!
- augroup END
- ]]
- require('cmp.utils.autocmd').emit('CmdlineLeave')
- end
-end)
-
-misc.set(_G, { 'cmp', 'plugin', 'colorscheme' }, function()
- highlight.inherit('CmpItemAbbrDefault', 'Pmenu', {
- guibg = 'NONE',
- ctermbg = 'NONE',
- })
- highlight.inherit('CmpItemAbbrDeprecatedDefault', 'Comment', {
- gui = 'NONE',
- guibg = 'NONE',
- ctermbg = 'NONE',
- })
- highlight.inherit('CmpItemAbbrMatchDefault', 'Pmenu', {
- gui = 'NONE',
- guibg = 'NONE',
- ctermbg = 'NONE',
- })
- highlight.inherit('CmpItemAbbrMatchFuzzyDefault', 'Pmenu', {
- gui = 'NONE',
- guibg = 'NONE',
- ctermbg = 'NONE',
- })
- highlight.inherit('CmpItemKindDefault', 'Special', {
- guibg = 'NONE',
- ctermbg = 'NONE',
- })
+autocmd.subscribe('ColorScheme', function()
+ highlight.inherit('CmpItemAbbrDefault', 'Pmenu', { bg = 'NONE', default = false })
+ highlight.inherit('CmpItemAbbrDeprecatedDefault', 'Comment', { bg = 'NONE', default = false })
+ highlight.inherit('CmpItemAbbrMatchDefault', 'Pmenu', { bg = 'NONE', default = false })
+ highlight.inherit('CmpItemAbbrMatchFuzzyDefault', 'Pmenu', { bg = 'NONE', default = false })
+ highlight.inherit('CmpItemKindDefault', 'Special', { bg = 'NONE', default = false })
+ highlight.inherit('CmpItemMenuDefault', 'Pmenu', { bg = 'NONE', default = false })
for name in pairs(types.lsp.CompletionItemKind) do
if type(name) == 'string' then
- vim.cmd(([[highlight default link CmpItemKind%sDefault CmpItemKind]]):format(name))
+ vim.api.nvim_set_hl(0, ('CmpItemKind%sDefault'):format(name), { link = 'CmpItemKind', default = false })
end
end
- highlight.inherit('CmpItemMenuDefault', 'Pmenu', {
- guibg = 'NONE',
- ctermbg = 'NONE',
- })
end)
-_G.cmp.plugin.colorscheme()
-
-if vim.fn.hlexists('CmpItemAbbr') ~= 1 then
- vim.cmd [[highlight default link CmpItemAbbr CmpItemAbbrDefault]]
-end
-
-if vim.fn.hlexists('CmpItemAbbrDeprecated') ~= 1 then
- vim.cmd [[highlight default link CmpItemAbbrDeprecated CmpItemAbbrDeprecatedDefault]]
-end
-
-if vim.fn.hlexists('CmpItemAbbrMatch') ~= 1 then
- vim.cmd [[highlight default link CmpItemAbbrMatch CmpItemAbbrMatchDefault]]
-end
-
-if vim.fn.hlexists('CmpItemAbbrMatchFuzzy') ~= 1 then
- vim.cmd [[highlight default link CmpItemAbbrMatchFuzzy CmpItemAbbrMatchFuzzyDefault]]
-end
-
-if vim.fn.hlexists('CmpItemKind') ~= 1 then
- vim.cmd [[highlight default link CmpItemKind CmpItemKindDefault]]
-end
-for name in pairs(types.lsp.CompletionItemKind) do
- if type(name) == 'string' then
- local hi = ('CmpItemKind%s'):format(name)
- if vim.fn.hlexists(hi) ~= 1 then
- vim.cmd(([[highlight default link %s %sDefault]]):format(hi, hi))
- end
- end
-end
-
-if vim.fn.hlexists('CmpItemMenu') ~= 1 then
- vim.cmd [[highlight default link CmpItemMenu CmpItemMenuDefault]]
-end
-
-vim.cmd [[command! CmpStatus lua require('cmp').status()]]
-
-vim.cmd [[doautocmd User CmpReady]]
+autocmd.emit('ColorScheme')
if vim.on_key then
vim.on_key(function(keys)
if keys == vim.api.nvim_replace_termcodes('', true, true, true) then
vim.schedule(function()
if not api.is_suitable_mode() then
- require('cmp.utils.autocmd').emit('InsertLeave')
+ autocmd.emit('InsertLeave')
end
end)
end
end, vim.api.nvim_create_namespace('cmp.plugin'))
end
+vim.api.nvim_create_user_command('CmpStatus', function()
+ require('cmp').status()
+end, { desc = 'Check status of cmp sources' })
+
+vim.cmd([[doautocmd User CmpReady]])
diff --git a/bundle/nvim-cmp/utils/install_stylua.sh b/bundle/nvim-cmp/utils/install_stylua.sh
deleted file mode 100644
index 963416ea0..000000000
--- a/bundle/nvim-cmp/utils/install_stylua.sh
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env bash
-
-set -eu pipefall
-
-declare -r INSTALL_DIR="$PWD/utils"
-declare -r RELEASE="0.10.0"
-declare -r OS="linux"
-# declare -r OS="$(uname -s)"
-declare -r FILENAME="stylua-$RELEASE-$OS"
-
-declare -a __deps=("curl" "unzip")
-
-function check_deps() {
- for dep in "${__deps[@]}"; do
- if ! command -v "$dep" >/dev/null; then
- echo "Missing depdendecy!"
- echo "The \"$dep\" command was not found!. Please install and try again."
- fi
- done
-}
-
-function download_stylua() {
- local DOWNLOAD_DIR
- local URL="https://github.com/JohnnyMorganz/StyLua/releases/download/v$RELEASE/$FILENAME.zip"
-
- DOWNLOAD_DIR="$(mktemp -d)"
- echo "Initiating download for Stylua v$RELEASE"
- if ! curl --progress-bar --fail -L "$URL" -o "$DOWNLOAD_DIR/$FILENAME.zip"; then
- echo "Download failed. Check that the release/filename are correct."
- exit 1
- fi
-
- echo "Installation in progress.."
- unzip -q "$DOWNLOAD_DIR/$FILENAME.zip" -d "$DOWNLOAD_DIR"
-
- if [ -f "$DOWNLOAD_DIR/stylua" ]; then
- mv "$DOWNLOAD_DIR/stylua" "$INSTALL_DIR/stylua"
- else
- mv "$DOWNLOAD_DIR/$FILENAME/stylua" "$INSTALL_DIR/."
- fi
-
- chmod u+x "$INSTALL_DIR/stylua"
-}
-
-function verify_install() {
- echo "Verifying installation.."
- local DOWNLOADED_VER
- DOWNLOADED_VER="$("$INSTALL_DIR/stylua" -V | awk '{ print $2 }')"
- if [ "$DOWNLOADED_VER" != "$RELEASE" ]; then
- echo "Mismatched version!"
- echo "Expected: v$RELEASE but got v$DOWNLOADED_VER"
- exit 1
- fi
- echo "Verification complete!"
-}
-
-function main() {
- check_deps
- download_stylua
- verify_install
-}
-
-main "$@"
diff --git a/bundle/nvim-cmp/utils/vimrc.vim b/bundle/nvim-cmp/utils/vimrc.vim
index a83e71d31..a18e3c36e 100644
--- a/bundle/nvim-cmp/utils/vimrc.vim
+++ b/bundle/nvim-cmp/utils/vimrc.vim
@@ -36,15 +36,15 @@ cmp.setup {
[''] = cmp.mapping.confirm({ select = true })
},
- sources = {
+ sources = cmp.config.sources({
{ name = "nvim_lsp" },
{ name = "buffer" },
- },
+ }),
}
EOF
lua << EOF
-local capabilities = require('cmp_nvim_lsp').update_capabilities(vim.lsp.protocol.make_client_capabilities())
+local capabilities = require('cmp_nvim_lsp').default_capabilities()
require'lspconfig'.cssls.setup {
capabilities = capabilities,