mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-23 10:40:03 +08:00
fix(bundle): update bundle nvim-cmp
close https://github.com/SpaceVim/SpaceVim/issues/4637
This commit is contained in:
parent
f1e57311ca
commit
207aa46f1d
@ -19,4 +19,4 @@ In `bundle/` directory, there are two kinds of plugins: forked plugins without c
|
||||
- [indent-blankline.nvim](https://github.com/lukas-reineke/indent-blankline.nvim/tree/17a83ea765831cb0cc64f768b8c3f43479b90bbe)
|
||||
- [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/tree/507f8a570ac2b8b8dabdd0f62da3b3194bf822f8)
|
||||
- [deoplete-lsp](https://github.com/deoplete-plugins/deoplete-lsp/tree/6299a22bedfb4f814d95cb0010291501472f8fd0)
|
||||
- [nvim-cmp](https://github.com/hrsh7th/nvim-cmp/tree/1cfe2f7dfdd877b54c0f4b0f9a15f525e7a3ea01)
|
||||
- [nvim-cmp](https://github.com/hrsh7th/nvim-cmp/tree/3192a0c57837c1ec5bf298e4f3ec984c7d2d60c0)
|
||||
|
@ -1,31 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- I will close the issue if this template was ignored. -->
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
|
||||
|
||||
**Minimal config based on [this](https://github.com/hrsh7th/nvim-cmp/blob/main/utils/vimrc.vim)**
|
||||
|
||||
```vim
|
||||
```
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
|
||||
|
||||
**Additional context**
|
71
bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
71
bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
name: Bug Report
|
||||
description: Report a problem in nvim-cmp
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: faq-prerequisite
|
||||
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.
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: issue-prerequisite
|
||||
attributes:
|
||||
label: Issues
|
||||
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.
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Neovim Version"
|
||||
description: "`nvim --version`:"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Minimal reproducible full config"
|
||||
description: |
|
||||
You must provide a working config based on [this](https://github.com/hrsh7th/nvim-cmp/blob/main/utils/vimrc.vim). Not part of config.
|
||||
1. Copy the base minimal config to the `~/cmp-repro.vim`
|
||||
2. Edit `~/cmp-repro.vim` for reproducing the issue
|
||||
3. Open `nvim -u ~/cmp-repro.vim`
|
||||
4. Check reproduction step
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Description"
|
||||
description: "Describe in detail what happens"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Steps to reproduce"
|
||||
description: "Full reproduction steps. Include a sample file if your issue relates to a specific filetype."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Expected behavior"
|
||||
description: "A description of the behavior you expected."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Actual behavior"
|
||||
description: "A description of the actual behavior."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Additional context"
|
||||
description: "Any other relevant information"
|
@ -26,6 +26,7 @@ jobs:
|
||||
- name: Setup neovim
|
||||
uses: rhysd/action-setup-vim@v1
|
||||
with:
|
||||
version: nightly
|
||||
neovim: true
|
||||
|
||||
- name: Setup lua
|
||||
|
2
bundle/nvim-cmp/.gitignore
vendored
2
bundle/nvim-cmp/.gitignore
vendored
@ -1 +1,3 @@
|
||||
doc/tags
|
||||
utils/stylua
|
||||
|
||||
|
@ -11,25 +11,18 @@ Readme!
|
||||
1. nvim-cmp's breaking changes are documented [here](https://github.com/hrsh7th/nvim-cmp/issues/231).
|
||||
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).
|
||||
|
||||
|
||||
|
||||
Concept
|
||||
====================
|
||||
|
||||
- Full support for LSP completion related capabilities
|
||||
- Powerful customizability via Lua functions
|
||||
- Smart handling of key mapping
|
||||
- No flicker
|
||||
- Works properly
|
||||
- Fully customizable via Lua functions
|
||||
- Fully supports LSP's completion capabilities
|
||||
- Snippets
|
||||
- CommitCharacters
|
||||
- TriggerCharacters
|
||||
- TextEdit and InsertReplaceTextEdit
|
||||
- AdditionalTextEdits
|
||||
- Markdown documentation
|
||||
- Execute commands (Some LSP server needs it to auto-importing. e.g. `sumneko_lua` or `purescript-language-server`)
|
||||
- Preselect
|
||||
- CompletionItemTags
|
||||
- Support pairs-wise plugin automatically
|
||||
|
||||
|
||||
|
||||
Setup
|
||||
@ -37,9 +30,9 @@ Setup
|
||||
|
||||
### Recommended Configuration
|
||||
|
||||
This example configuration uses `vim-plug` as the plugin manager.
|
||||
This example configuration uses `vim-plug` as the plugin manager and `vim-vsnip` as snippet plugin.
|
||||
|
||||
```viml
|
||||
```lua
|
||||
call plug#begin(s:plug_dir)
|
||||
Plug 'neovim/nvim-lspconfig'
|
||||
Plug 'hrsh7th/cmp-nvim-lsp'
|
||||
@ -78,12 +71,12 @@ lua <<EOF
|
||||
expand = function(args)
|
||||
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
|
||||
-- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
|
||||
-- require('snippy').expand_snippet(args.body) -- For `snippy` users.
|
||||
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
|
||||
-- require'snippy'.expand_snippet(args.body) -- For `snippy` users.
|
||||
end,
|
||||
},
|
||||
mapping = {
|
||||
['<C-d>'] = cmp.mapping(cmp.mapping.scroll_docs(-4), { 'i', 'c' }),
|
||||
['<C-b>'] = cmp.mapping(cmp.mapping.scroll_docs(-4), { 'i', 'c' }),
|
||||
['<C-f>'] = cmp.mapping(cmp.mapping.scroll_docs(4), { 'i', 'c' }),
|
||||
['<C-Space>'] = cmp.mapping(cmp.mapping.complete(), { 'i', 'c' }),
|
||||
['<C-y>'] = cmp.config.disable, -- Specify `cmp.config.disable` if you want to remove the default `<C-y>` mapping.
|
||||
@ -91,7 +84,7 @@ lua <<EOF
|
||||
i = cmp.mapping.abort(),
|
||||
c = cmp.mapping.close(),
|
||||
}),
|
||||
['<CR>'] = cmp.mapping.confirm({ select = true }),
|
||||
['<CR>'] = 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' },
|
||||
@ -104,6 +97,15 @@ lua <<EOF
|
||||
})
|
||||
})
|
||||
|
||||
-- Set configuration for specific filetype.
|
||||
cmp.setup.filetype('gitcommit', {
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'cmp_git' }, -- You can specify the `cmp_git` source if you were installed it.
|
||||
}, {
|
||||
{ name = 'buffer' },
|
||||
})
|
||||
})
|
||||
|
||||
-- Use buffer source for `/` (if you enabled `native_menu`, this won't work anymore).
|
||||
cmp.setup.cmdline('/', {
|
||||
sources = {
|
||||
@ -131,646 +133,83 @@ EOF
|
||||
|
||||
### Where can I find more completion sources?
|
||||
|
||||
You can search for various completion sources [here](https://github.com/topics/nvim-cmp).
|
||||
A list of available sources can be found in the [Wiki](https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources) or by searching for projects that match the nvim-cmp [GitHub topic](https://github.com/topics/nvim-cmp).
|
||||
|
||||
### Where can I find advanced configuration examples?
|
||||
|
||||
Please see the corresponding [FAQ](#how-to-show-name-of-item-kind-and-source-like-compe) section or [Wiki pages](https://github.com/hrsh7th/nvim-cmp/wiki).
|
||||
|
||||
|
||||
Configuration options
|
||||
Advanced configuration example
|
||||
====================
|
||||
|
||||
You can specify the following configuration options via `cmp.setup { ... }`.
|
||||
### Use nvim-cmp as smart omnifunc handler.
|
||||
|
||||
The configuration options will be merged with the [default config](./lua/cmp/config/default.lua).
|
||||
|
||||
If you want to remove a default option, set it to `false`.
|
||||
|
||||
|
||||
#### mapping (type: table<string, fun(fallback: function)>)
|
||||
|
||||
Defines the action of each key mapping. The following lists all the built-in actions:
|
||||
|
||||
- `cmp.mapping.select_prev_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
- `cmp.mapping.select_next_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
- `cmp.mapping.scroll_docs(number)`
|
||||
- `cmp.mapping.complete()`
|
||||
- `cmp.mapping.close()`
|
||||
- `cmp.mapping.abort()`
|
||||
- `cmp.mapping.confirm({ select = bool, behavior = cmp.ConfirmBehavior.{Insert,Replace} })`: If `select` is true and you haven't select any item, automatically selects the first item.
|
||||
|
||||
You can configure `nvim-cmp` to use these `cmp.mapping` like this:
|
||||
nvim-cmp can be used as flexible omnifunc manager.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<C-n>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }),
|
||||
['<C-p>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }),
|
||||
['<Down>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<Up>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<C-d>'] = cmp.mapping.scroll_docs(-4),
|
||||
['<C-f>'] = cmp.mapping.scroll_docs(4),
|
||||
['<C-Space>'] = cmp.mapping.complete(),
|
||||
['<C-e>'] = cmp.mapping.close(),
|
||||
['<CR>'] = cmp.mapping.confirm({
|
||||
behavior = cmp.ConfirmBehavior.Replace,
|
||||
select = true,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
In addition, the mapping mode can be specified with the help of `cmp.mapping(...)`. The default is the insert mode (i) if not specified.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
...
|
||||
['<Tab>'] = cmp.mapping(cmp.mapping.select_next_item(), { 'i', 's' })
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The mapping mode can also be specified using a table. This is particularly useful to set different actions for each mode.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<CR>'] = cmp.mapping({
|
||||
i = cmp.mapping.confirm({ select = true }),
|
||||
c = cmp.mapping.confirm({ select = false }),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You can also provide a custom function as the action.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<Tab>'] = function(fallback)
|
||||
if ...some_condition... then
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('...', true, true, true), 'n', true)
|
||||
else
|
||||
fallback() -- The fallback function is treated as original mapped key. In this case, it might be `<Tab>`.
|
||||
end
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
#### enabled (type: fun(): boolean|boolean)
|
||||
|
||||
A boolean value, or a function returning a boolean, that specifies whether to enable nvim-cmp's features or not.
|
||||
|
||||
Default:
|
||||
|
||||
```lua
|
||||
function()
|
||||
return vim.api.nvim_buf_get_option(0, 'buftype') ~= 'prompt'
|
||||
end
|
||||
```
|
||||
|
||||
#### sources (type: table<cmp.SourceConfig>)
|
||||
|
||||
Lists all the global completion sources that will be enabled in all buffers.
|
||||
The order of the list defines the priority of each source. See the
|
||||
*sorting.priority_weight* option below.
|
||||
|
||||
It is possible to set up different sources for different filetypes using
|
||||
`FileType` autocommand and `cmp.setup.buffer` to override the global
|
||||
configuration.
|
||||
|
||||
```viml
|
||||
" Setup buffer configuration (nvim-lua source only enables in Lua filetype).
|
||||
autocmd FileType lua lua require'cmp'.setup.buffer {
|
||||
\ sources = {
|
||||
\ { name = 'nvim_lua' },
|
||||
\ { name = 'buffer' },
|
||||
\ },
|
||||
\ }
|
||||
```
|
||||
|
||||
Note that the source name isn't necessarily the source repository name. Source
|
||||
names are defined in the source repository README files. For example, look at
|
||||
the [hrsh7th/cmp-buffer](https://github.com/hrsh7th/cmp-buffer) source README
|
||||
which defines the source name as `buffer`.
|
||||
|
||||
#### sources[number].name (type: string)
|
||||
|
||||
The source name.
|
||||
|
||||
#### sources[number].opts (type: table)
|
||||
|
||||
The source customization options. It is defined by each source.
|
||||
|
||||
#### sources[number].priority (type: number|nil)
|
||||
|
||||
The priority of the source. If you don't specify it, the source priority will
|
||||
be determined by the default algorithm (see `sorting.priority_weight`).
|
||||
|
||||
#### sources[number].keyword_pattern (type: string)
|
||||
|
||||
The source specific keyword_pattern for override.
|
||||
|
||||
#### sources[number].keyword_length (type: number)
|
||||
|
||||
The source specific keyword_length for override.
|
||||
|
||||
#### sources[number].max_item_count (type: number)
|
||||
|
||||
The source specific maximum item count.
|
||||
|
||||
#### sources[number].group_index (type: number)
|
||||
|
||||
The source group index.
|
||||
|
||||
You can call built-in utility like `cmp.config.sources({ { name = 'a' } }, { { name = 'b' } })`.
|
||||
|
||||
#### preselect (type: cmp.PreselectMode)
|
||||
|
||||
Specify preselect mode. The following modes are available.
|
||||
|
||||
- `cmp.PreselectMode.Item`
|
||||
- If the item has `preselect = true`, `nvim-cmp` will preselect it.
|
||||
- `cmp.PreselectMode.None`
|
||||
- Disable preselect feature.
|
||||
|
||||
Default: `cmp.PreselectMode.Item`
|
||||
|
||||
#### completion.autocomplete (type: cmp.TriggerEvent[])
|
||||
|
||||
Which events should trigger `autocompletion`.
|
||||
|
||||
If you set this to `false`, `nvim-cmp` will not perform completion
|
||||
automatically. You can still use manual completion though (like omni-completion
|
||||
via the `cmp.mapping.complete` function).
|
||||
|
||||
Default: `{ types.cmp.TriggerEvent.TextChanged }`
|
||||
|
||||
#### completion.keyword_pattern (type: string)
|
||||
|
||||
The default keyword pattern. This value will be used if a source does not set
|
||||
a source specific pattern.
|
||||
|
||||
Default: `[[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]]`
|
||||
|
||||
#### completion.keyword_length (type: number)
|
||||
|
||||
The minimum length of a word to complete on; e.g., do not try to complete when the
|
||||
length of the word to the left of the cursor is less than `keyword_length`.
|
||||
|
||||
Default: `1`
|
||||
|
||||
#### completion.get_trigger_characters (type: fun(trigger_characters: string[]): string[])
|
||||
|
||||
The function to resolve trigger_characters.
|
||||
|
||||
Default: `function(trigger_characters) return trigger_characters end`
|
||||
|
||||
#### completion.completeopt (type: string)
|
||||
|
||||
vim's `completeopt` setting. Warning: Be careful when changing this value.
|
||||
|
||||
Default: `menu,menuone,noselect`
|
||||
|
||||
#### confirmation.default_behavior (type: cmp.ConfirmBehavior)
|
||||
|
||||
A default `cmp.ConfirmBehavior` value when to use confirmed by commitCharacters
|
||||
|
||||
Default: `cmp.ConfirmBehavior.Insert`
|
||||
|
||||
#### confirmation.get_commit_characters (type: fun(commit_characters: string[]): string[])
|
||||
|
||||
The function to resolve commit_characters.
|
||||
|
||||
#### sorting.priority_weight (type: number)
|
||||
|
||||
The score multiplier of source when calculating the items' priorities.
|
||||
Specifically, each item's original priority (given by its corresponding source)
|
||||
will be increased by `#sources - (source_index - 1)` multiplied by
|
||||
`priority_weight`. That is, the final priority is calculated by the following formula:
|
||||
|
||||
`final_score = orig_score + ((#sources - (source_index - 1)) * sorting.priority_weight)`
|
||||
|
||||
Default: `2`
|
||||
|
||||
#### sorting.comparators (type: function[])
|
||||
|
||||
When sorting completion items, the sort logic tries each function in
|
||||
`sorting.comparators` consecutively when comparing two items. The first function
|
||||
to return something other than `nil` takes precedence.
|
||||
|
||||
Each function must return `boolean|nil`.
|
||||
|
||||
You can use the preset functions from `cmp.config.compare.*`.
|
||||
|
||||
Default:
|
||||
```lua
|
||||
{
|
||||
cmp.config.compare.offset,
|
||||
cmp.config.compare.exact,
|
||||
cmp.config.compare.score,
|
||||
cmp.config.compare.recently_used,
|
||||
cmp.config.compare.kind,
|
||||
cmp.config.compare.sort_text,
|
||||
cmp.config.compare.length,
|
||||
cmp.config.compare.order,
|
||||
}
|
||||
```
|
||||
|
||||
#### documentation (type: false | cmp.DocumentationConfig)
|
||||
|
||||
If set to `false`, the documentation of each item will not be shown.
|
||||
Else, a table representing documentation configuration should be provided.
|
||||
The following are the possible options:
|
||||
|
||||
#### documentation.border (type: string[])
|
||||
|
||||
Border characters used for documentation window.
|
||||
|
||||
#### documentation.winhighlight (type: string)
|
||||
|
||||
A neovim's `winhighlight` option for documentation window.
|
||||
|
||||
#### documentation.maxwidth (type: number)
|
||||
|
||||
The documentation window's max width.
|
||||
|
||||
#### documentation.maxheight (type: number)
|
||||
|
||||
The documentation window's max height.
|
||||
|
||||
#### documentation.zindex (type: number)
|
||||
|
||||
The documentation window's zindex.
|
||||
|
||||
#### formatting.fields (type: cmp.ItemField[])
|
||||
|
||||
The order of item's fields for completion menu.
|
||||
|
||||
#### formatting.format (type: fun(entry: cmp.Entry, vim_item: vim.CompletedItem): vim.CompletedItem)
|
||||
|
||||
A function to customize completion menu.
|
||||
The return value is defined by vim. See `:help complete-items`.
|
||||
|
||||
You can display the fancy icons to completion-menu with [lspkind-nvim](https://github.com/onsails/lspkind-nvim).
|
||||
|
||||
Please see [FAQ](#how-to-show-name-of-item-kind-and-source-like-compe) if you would like to show symbol-text (e.g. function) and source (e.g. LSP) like compe.
|
||||
|
||||
```lua
|
||||
local lspkind = require('lspkind')
|
||||
local cmp = require('cmp')
|
||||
cmp.setup {
|
||||
formatting = {
|
||||
format = lspkind.cmp_format(),
|
||||
completion = {
|
||||
autocomplete = false, -- disable auto-completion.
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See the [wiki](https://github.com/hrsh7th/nvim-cmp/wiki/Menu-Appearance#basic-customisations) for more info on customizing menu appearance.
|
||||
|
||||
#### experimental.native_menu (type: boolean)
|
||||
|
||||
Use vim's native completion menu instead of custom floating menu.
|
||||
|
||||
Default: `false`
|
||||
|
||||
#### experimental.ghost_text (type: cmp.GhostTextConfig | false)
|
||||
|
||||
Specify whether to display ghost text.
|
||||
|
||||
Default: `false`
|
||||
|
||||
Commands
|
||||
====================
|
||||
|
||||
#### `CmpStatus`
|
||||
|
||||
Show the source statuses
|
||||
|
||||
Autocmds
|
||||
====================
|
||||
|
||||
#### `cmp#ready`
|
||||
|
||||
Invoke after nvim-cmp setup.
|
||||
|
||||
Highlights
|
||||
====================
|
||||
|
||||
#### `CmpItemAbbr`
|
||||
|
||||
The abbr field.
|
||||
|
||||
#### `CmpItemAbbrDeprecated`
|
||||
|
||||
The deprecated item's abbr field.
|
||||
|
||||
#### `CmpItemAbbrMatch`
|
||||
|
||||
The matched characters highlight.
|
||||
|
||||
#### `CmpItemAbbrMatchFuzzy`
|
||||
|
||||
The fuzzy matched characters highlight.
|
||||
|
||||
#### `CmpItemKind`
|
||||
|
||||
The kind field.
|
||||
|
||||
#### `CmpItemMenu`
|
||||
|
||||
The menu field.
|
||||
|
||||
Programatic API
|
||||
====================
|
||||
|
||||
You can use the following APIs.
|
||||
|
||||
#### `cmp.event:on(name: string, callback: string)`
|
||||
|
||||
Subscribes to the following events.
|
||||
|
||||
- `confirm_done`
|
||||
|
||||
#### `cmp.get_config()`
|
||||
|
||||
Returns the current configuration.
|
||||
|
||||
#### `cmp.visible()`
|
||||
|
||||
Returns the completion menu is visible or not.
|
||||
|
||||
NOTE: This method returns true if the native popup menu is visible, for the convenience of defining mappings.
|
||||
|
||||
#### `cmp.get_selected_entry()`
|
||||
|
||||
Returns the selected entry.
|
||||
|
||||
#### `cmp.get_active_entry()`
|
||||
|
||||
Returns the active entry.
|
||||
|
||||
NOTE: The `preselected` entry does not returned from this method.
|
||||
|
||||
#### `cmp.confirm({ select = boolean, behavior = cmp.ConfirmBehavior.{Insert,Replace} }, callback)`
|
||||
|
||||
Confirms the current selected item, if possible. If `select` is true and no item has been selected, selects the first item.
|
||||
|
||||
#### `cmp.complete()`
|
||||
|
||||
Invokes manual completion.
|
||||
|
||||
#### `cmp.close()`
|
||||
|
||||
Closes the current completion menu.
|
||||
|
||||
#### `cmp.abort()`
|
||||
|
||||
Closes the current completion menu and restore the current line (similar to native `<C-e>` behavior).
|
||||
|
||||
#### `cmp.select_next_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
|
||||
Selects the next completion item if possible.
|
||||
|
||||
#### `cmp.select_prev_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
|
||||
Selects the previous completion item if possible.
|
||||
|
||||
#### `cmp.scroll_docs(delta)`
|
||||
|
||||
Scrolls the documentation window by `delta` lines, if possible.
|
||||
|
||||
|
||||
FAQ
|
||||
====================
|
||||
|
||||
#### I can't get the specific source working.
|
||||
|
||||
Check the output of command `:CmpStatus`. It is likely that you specify the source name incorrectly.
|
||||
|
||||
NOTE: `nvim_lsp` will be sourced on `InsertEnter` event. It will show as `unknown source`, but this isn't a problem.
|
||||
|
||||
|
||||
#### What is the `pair-wise plugin automatically supported`?
|
||||
|
||||
Some pair-wise plugin set up the mapping automatically.
|
||||
For example, `vim-endwise` will map `<CR>` even if you don't do any mapping instructions for the plugin.
|
||||
|
||||
But I think the user want to override `<CR>` mapping only when the mapping item is selected.
|
||||
|
||||
The `nvim-cmp` does it automatically.
|
||||
|
||||
The following configuration will be working as
|
||||
|
||||
1. If the completion-item is selected, will be working as `cmp.mapping.confirm`.
|
||||
2. If the completion-item isn't selected, will be working as vim-endwise feature.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<CR>'] = cmp.mapping.confirm()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### What is the equivalence of nvim-compe's `preselect = 'always'`?
|
||||
|
||||
You can use the following configuration.
|
||||
|
||||
```lua
|
||||
cmp.setup {
|
||||
completion = {
|
||||
completeopt = 'menu,menuone,noinsert',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### I don't use a snippet plugin.
|
||||
|
||||
At the moment, nvim-cmp requires a snippet engine to function correctly.
|
||||
You need to specify one in `snippet`.
|
||||
|
||||
```lua
|
||||
snippet = {
|
||||
-- REQUIRED - you must specify a snippet engine
|
||||
expand = function(args)
|
||||
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
|
||||
-- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
|
||||
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
|
||||
-- require'snippy'.expand_snippet(args.body) -- For `snippy` users.
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### I dislike auto-completion
|
||||
|
||||
You can use `nvim-cmp` without auto-completion like this.
|
||||
|
||||
```lua
|
||||
cmp.setup {
|
||||
completion = {
|
||||
autocomplete = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### How to disable nvim-cmp on the specific buffer?
|
||||
|
||||
You can specify `enabled = false` like this.
|
||||
|
||||
```vim
|
||||
autocmd FileType TelescopePrompt lua require('cmp').setup.buffer { enabled = false }
|
||||
```
|
||||
|
||||
|
||||
#### nvim-cmp is slow.
|
||||
|
||||
I've optimized `nvim-cmp` as much as possible, but there are currently some known / unfixable issues.
|
||||
|
||||
**`cmp-buffer` source and too large buffer**
|
||||
|
||||
The `cmp-buffer` source makes an index of the current buffer so if the current buffer is too large, it will slowdown the main UI thread.
|
||||
|
||||
**`vim.lsp.set_log_level`**
|
||||
|
||||
This setting will cause the filesystem operation for each LSP payload.
|
||||
This will greatly slow down nvim-cmp (and other LSP related features).
|
||||
|
||||
|
||||
#### How to show name of item kind and source (like compe)?
|
||||
|
||||
```lua
|
||||
formatting = {
|
||||
format = require("lspkind").cmp_format({with_text = true, menu = ({
|
||||
buffer = "[Buffer]",
|
||||
nvim_lsp = "[LSP]",
|
||||
luasnip = "[LuaSnip]",
|
||||
nvim_lua = "[Lua]",
|
||||
latex_symbols = "[Latex]",
|
||||
})}),
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
#### How to set up mappings?
|
||||
|
||||
You can find all the mapping examples in [Example mappings](https://github.com/hrsh7th/nvim-cmp/wiki/Example-mappings).
|
||||
|
||||
|
||||
Create a Custom Source
|
||||
====================
|
||||
|
||||
Warning: If the LSP spec is changed, nvim-cmp will keep up to it without an announcement.
|
||||
|
||||
If you publish `nvim-cmp` source to GitHub, please add `nvim-cmp` topic for the repo.
|
||||
|
||||
You should read [cmp types](/lua/cmp/types) and [LSP spec](https://microsoft.github.io/language-server-protocol/specifications/specification-current/) to create sources.
|
||||
|
||||
- The `complete` function is required. Others can be omitted.
|
||||
- The `callback` argument must always be called.
|
||||
- The custom source should only use `require('cmp')`.
|
||||
- The custom source can specify `word` property to CompletionItem. (It isn't an LSP specification but supported as a special case.)
|
||||
|
||||
Here is an example of a custom source.
|
||||
|
||||
```lua
|
||||
local source = {}
|
||||
|
||||
---Source constructor.
|
||||
source.new = function()
|
||||
local self = setmetatable({}, { __index = source })
|
||||
self.your_awesome_variable = 1
|
||||
return self
|
||||
_G.vimrc = _G.vimrc or {}
|
||||
_G.vimrc.cmp = _G.vimrc.cmp or {}
|
||||
_G.vimrc.cmp.lsp = function()
|
||||
cmp.complete({
|
||||
config = {
|
||||
sources = {
|
||||
{ name = 'nvim_lsp' }
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
---Return the source is available or not.
|
||||
---@return boolean
|
||||
function source:is_available()
|
||||
return true
|
||||
end
|
||||
|
||||
---Return the source name for some information.
|
||||
function source:get_debug_name()
|
||||
return 'example'
|
||||
end
|
||||
|
||||
---Return keyword pattern which will be used...
|
||||
--- 1. Trigger keyword completion
|
||||
--- 2. Detect menu start offset
|
||||
--- 3. Reset completion state
|
||||
---@param params cmp.SourceBaseApiParams
|
||||
---@return string
|
||||
function source:get_keyword_pattern(params)
|
||||
return '???'
|
||||
end
|
||||
|
||||
---Return trigger characters.
|
||||
---@param params cmp.SourceBaseApiParams
|
||||
---@return string[]
|
||||
function source:get_trigger_characters(params)
|
||||
return { ??? }
|
||||
end
|
||||
|
||||
---Invoke completion (required).
|
||||
--- If you want to abort completion, just call the callback without arguments.
|
||||
---@param params cmp.SourceCompletionApiParams
|
||||
---@param callback fun(response: lsp.CompletionResponse|nil)
|
||||
function source:complete(params, callback)
|
||||
callback({
|
||||
{ label = 'January' },
|
||||
{ label = 'February' },
|
||||
{ label = 'March' },
|
||||
{ label = 'April' },
|
||||
{ label = 'May' },
|
||||
{ label = 'June' },
|
||||
{ label = 'July' },
|
||||
{ label = 'August' },
|
||||
{ label = 'September' },
|
||||
{ label = 'October' },
|
||||
{ label = 'November' },
|
||||
{ label = 'December' },
|
||||
_G.vimrc.cmp.snippet = function()
|
||||
cmp.complete({
|
||||
config = {
|
||||
sources = {
|
||||
{ name = 'vsnip' }
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
---Resolve completion item that will be called when the item selected or before the item confirmation.
|
||||
---@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 that will be called when after the item confirmation.
|
||||
---@param completion_item lsp.CompletionItem
|
||||
---@param callback fun(completion_item: lsp.CompletionItem|nil)
|
||||
function source:execute(completion_item, callback)
|
||||
callback(completion_item)
|
||||
end
|
||||
|
||||
require('cmp').register_source(source.new())
|
||||
vim.cmd([[
|
||||
inoremap <C-x><C-o> <Cmd>lua vimrc.cmp.lsp()<CR>
|
||||
inoremap <C-x><C-s> <Cmd>lua vimrc.cmp.snippet()<CR>
|
||||
]])
|
||||
```
|
||||
|
||||
You can also create a source by Vim script like this (This is useful to support callback style plugins).
|
||||
### Full managed completion behavior.
|
||||
|
||||
- If you want to return `boolean`, you must return `v:true`/`v:false` instead of `0`/`1`.
|
||||
```lua
|
||||
local cmp = require('cmp')
|
||||
|
||||
```vim
|
||||
let s:source = {}
|
||||
cmp.setup {
|
||||
completion = {
|
||||
autocomplete = false, -- disable auto-completion.
|
||||
}
|
||||
}
|
||||
|
||||
function! s:source.new() abort
|
||||
return extend(deepcopy(s:source))
|
||||
endfunction
|
||||
|
||||
" The other APIs are also available.
|
||||
|
||||
function! s:source.complete(params, callback) abort
|
||||
call a:callback({
|
||||
\ { 'label': 'January' },
|
||||
\ { 'label': 'February' },
|
||||
\ { 'label': 'March' },
|
||||
\ { 'label': 'April' },
|
||||
\ { 'label': 'May' },
|
||||
\ { 'label': 'June' },
|
||||
\ { 'label': 'July' },
|
||||
\ { 'label': 'August' },
|
||||
\ { 'label': 'September' },
|
||||
\ { 'label': 'October' },
|
||||
\ { 'label': 'November' },
|
||||
\ { 'label': 'December' },
|
||||
\ })
|
||||
endfunction
|
||||
|
||||
call cmp#register_source('month', s:source.new())
|
||||
_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
|
||||
]])
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -1,18 +1,6 @@
|
||||
let s:bridge_id = 0
|
||||
let s:sources = {}
|
||||
|
||||
"
|
||||
" cmp#apply_text_edits
|
||||
"
|
||||
" TODO: Remove this if nvim's apply_text_edits will be improved.
|
||||
"
|
||||
function! cmp#apply_text_edits(bufnr, text_edits) abort
|
||||
if !exists('s:TextEdit')
|
||||
let s:TextEdit = vital#cmp#import('VS.LSP.TextEdit')
|
||||
endif
|
||||
call s:TextEdit.apply(a:bufnr, a:text_edits)
|
||||
endfunction
|
||||
|
||||
"
|
||||
" cmp#register_source
|
||||
"
|
||||
|
@ -1,9 +0,0 @@
|
||||
let s:_plugin_name = expand('<sfile>:t:r')
|
||||
|
||||
function! vital#{s:_plugin_name}#new() abort
|
||||
return vital#{s:_plugin_name[1:]}#new()
|
||||
endfunction
|
||||
|
||||
function! vital#{s:_plugin_name}#function(funcname) abort
|
||||
silent! return function(a:funcname)
|
||||
endfunction
|
@ -1,62 +0,0 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_cmp#VS#LSP#Position#import() abort', printf("return map({'cursor': '', 'vim_to_lsp': '', 'lsp_to_vim': ''}, \"vital#_cmp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
"
|
||||
" cursor
|
||||
"
|
||||
function! s:cursor() abort
|
||||
return s:vim_to_lsp('%', getpos('.')[1 : 3])
|
||||
endfunction
|
||||
|
||||
"
|
||||
" vim_to_lsp
|
||||
"
|
||||
function! s:vim_to_lsp(expr, pos) abort
|
||||
let l:line = s:_get_buffer_line(a:expr, a:pos[0])
|
||||
if l:line is v:null
|
||||
return {
|
||||
\ 'line': a:pos[0] - 1,
|
||||
\ 'character': a:pos[1] - 1
|
||||
\ }
|
||||
endif
|
||||
|
||||
return {
|
||||
\ 'line': a:pos[0] - 1,
|
||||
\ 'character': strchars(strpart(l:line, 0, a:pos[1] - 1))
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
"
|
||||
" lsp_to_vim
|
||||
"
|
||||
function! s:lsp_to_vim(expr, position) abort
|
||||
let l:line = s:_get_buffer_line(a:expr, a:position.line + 1)
|
||||
if l:line is v:null
|
||||
return [a:position.line + 1, a:position.character + 1]
|
||||
endif
|
||||
return [a:position.line + 1, byteidx(l:line, a:position.character) + 1]
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _get_buffer_line
|
||||
"
|
||||
function! s:_get_buffer_line(expr, lnum) abort
|
||||
try
|
||||
let l:expr = bufnr(a:expr)
|
||||
catch /.*/
|
||||
let l:expr = a:expr
|
||||
endtry
|
||||
if bufloaded(l:expr)
|
||||
return get(getbufline(l:expr, a:lnum), 0, v:null)
|
||||
elseif filereadable(a:expr)
|
||||
return get(readfile(a:expr, '', a:lnum), 0, v:null)
|
||||
endif
|
||||
return v:null
|
||||
endfunction
|
||||
|
@ -1,23 +0,0 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_cmp#VS#LSP#Text#import() abort', printf("return map({'normalize_eol': '', 'split_by_eol': ''}, \"vital#_cmp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
"
|
||||
" normalize_eol
|
||||
"
|
||||
function! s:normalize_eol(text) abort
|
||||
return substitute(a:text, "\r\n\\|\r", "\n", 'g')
|
||||
endfunction
|
||||
|
||||
"
|
||||
" split_by_eol
|
||||
"
|
||||
function! s:split_by_eol(text) abort
|
||||
return split(a:text, "\r\n\\|\r\\|\n", v:true)
|
||||
endfunction
|
||||
|
@ -1,185 +0,0 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_cmp#VS#LSP#TextEdit#import() abort', printf("return map({'_vital_depends': '', 'apply': '', '_vital_loaded': ''}, \"vital#_cmp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
"
|
||||
" _vital_loaded
|
||||
"
|
||||
function! s:_vital_loaded(V) abort
|
||||
let s:Text = a:V.import('VS.LSP.Text')
|
||||
let s:Position = a:V.import('VS.LSP.Position')
|
||||
let s:Buffer = a:V.import('VS.Vim.Buffer')
|
||||
let s:Option = a:V.import('VS.Vim.Option')
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _vital_depends
|
||||
"
|
||||
function! s:_vital_depends() abort
|
||||
return ['VS.LSP.Text', 'VS.LSP.Position', 'VS.Vim.Buffer', 'VS.Vim.Option']
|
||||
endfunction
|
||||
|
||||
"
|
||||
" apply
|
||||
"
|
||||
function! s:apply(path, text_edits) abort
|
||||
let l:current_bufname = bufname('%')
|
||||
let l:current_position = s:Position.cursor()
|
||||
|
||||
let l:target_bufnr = s:_switch(a:path)
|
||||
call s:_substitute(l:target_bufnr, a:text_edits, l:current_position)
|
||||
let l:current_bufnr = s:_switch(l:current_bufname)
|
||||
|
||||
if l:current_bufnr == l:target_bufnr
|
||||
call cursor(s:Position.lsp_to_vim('%', l:current_position))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _substitute
|
||||
"
|
||||
function! s:_substitute(bufnr, text_edits, current_position) abort
|
||||
try
|
||||
" Save state.
|
||||
let l:Restore = s:Option.define({
|
||||
\ 'foldenable': '0',
|
||||
\ })
|
||||
let l:view = winsaveview()
|
||||
|
||||
" Apply substitute.
|
||||
let [l:fixeol, l:text_edits] = s:_normalize(a:bufnr, a:text_edits)
|
||||
for l:text_edit in l:text_edits
|
||||
let l:start = s:Position.lsp_to_vim(a:bufnr, l:text_edit.range.start)
|
||||
let l:end = s:Position.lsp_to_vim(a:bufnr, l:text_edit.range.end)
|
||||
let l:text = s:Text.normalize_eol(l:text_edit.newText)
|
||||
execute printf('noautocmd keeppatterns keepjumps silent %ssubstitute/\%%%sl\%%%sc\_.\{-}\%%%sl\%%%sc/\=l:text/%se',
|
||||
\ l:start[0],
|
||||
\ l:start[0],
|
||||
\ l:start[1],
|
||||
\ l:end[0],
|
||||
\ l:end[1],
|
||||
\ &gdefault ? 'g' : ''
|
||||
\ )
|
||||
call s:_fix_cursor_position(a:current_position, l:text_edit, s:Text.split_by_eol(l:text))
|
||||
endfor
|
||||
|
||||
" Remove last empty line if fixeol enabled.
|
||||
if l:fixeol && getline('$') ==# ''
|
||||
noautocmd keeppatterns keepjumps silent $delete _
|
||||
endif
|
||||
catch /.*/
|
||||
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
|
||||
finally
|
||||
" Restore state.
|
||||
call l:Restore()
|
||||
call winrestview(l:view)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _fix_cursor_position
|
||||
"
|
||||
function! s:_fix_cursor_position(position, text_edit, lines) abort
|
||||
let l:lines_len = len(a:lines)
|
||||
let l:range_len = (a:text_edit.range.end.line - a:text_edit.range.start.line) + 1
|
||||
|
||||
if a:text_edit.range.end.line < a:position.line
|
||||
let a:position.line += l:lines_len - l:range_len
|
||||
elseif a:text_edit.range.end.line == a:position.line && a:text_edit.range.end.character <= a:position.character
|
||||
let a:position.line += l:lines_len - l:range_len
|
||||
let a:position.character = strchars(a:lines[-1]) + (a:position.character - a:text_edit.range.end.character)
|
||||
if l:lines_len == 1
|
||||
let a:position.character += a:text_edit.range.start.character
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _normalize
|
||||
"
|
||||
function! s:_normalize(bufnr, text_edits) abort
|
||||
let l:text_edits = type(a:text_edits) == type([]) ? a:text_edits : [a:text_edits]
|
||||
let l:text_edits = s:_range(l:text_edits)
|
||||
let l:text_edits = sort(l:text_edits, function('s:_compare'))
|
||||
let l:text_edits = reverse(l:text_edits)
|
||||
return s:_fix_text_edits(a:bufnr, l:text_edits)
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _range
|
||||
"
|
||||
function! s:_range(text_edits) abort
|
||||
let l:text_edits = []
|
||||
for l:text_edit in a:text_edits
|
||||
if type(l:text_edit) != type({})
|
||||
continue
|
||||
endif
|
||||
if l:text_edit.range.start.line > l:text_edit.range.end.line || (
|
||||
\ l:text_edit.range.start.line == l:text_edit.range.end.line &&
|
||||
\ l:text_edit.range.start.character > l:text_edit.range.end.character
|
||||
\ )
|
||||
let l:text_edit.range = { 'start': l:text_edit.range.end, 'end': l:text_edit.range.start }
|
||||
endif
|
||||
let l:text_edits += [l:text_edit]
|
||||
endfor
|
||||
return l:text_edits
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _compare
|
||||
"
|
||||
function! s:_compare(text_edit1, text_edit2) abort
|
||||
let l:diff = a:text_edit1.range.start.line - a:text_edit2.range.start.line
|
||||
if l:diff == 0
|
||||
return a:text_edit1.range.start.character - a:text_edit2.range.start.character
|
||||
endif
|
||||
return l:diff
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _fix_text_edits
|
||||
"
|
||||
function! s:_fix_text_edits(bufnr, text_edits) abort
|
||||
let l:max = s:Buffer.get_line_count(a:bufnr)
|
||||
|
||||
let l:fixeol = v:false
|
||||
let l:text_edits = []
|
||||
for l:text_edit in a:text_edits
|
||||
if l:max <= l:text_edit.range.start.line
|
||||
let l:text_edit.range.start.line = l:max - 1
|
||||
let l:text_edit.range.start.character = strchars(get(getbufline(a:bufnr, '$'), 0, ''))
|
||||
let l:text_edit.newText = "\n" . l:text_edit.newText
|
||||
let l:fixeol = &fixendofline && !&binary
|
||||
endif
|
||||
if l:max <= l:text_edit.range.end.line
|
||||
let l:text_edit.range.end.line = l:max - 1
|
||||
let l:text_edit.range.end.character = strchars(get(getbufline(a:bufnr, '$'), 0, ''))
|
||||
let l:fixeol = &fixendofline && !&binary
|
||||
endif
|
||||
call add(l:text_edits, l:text_edit)
|
||||
endfor
|
||||
|
||||
return [l:fixeol, l:text_edits]
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _switch
|
||||
"
|
||||
function! s:_switch(path) abort
|
||||
let l:curr = bufnr('%')
|
||||
let l:next = bufnr(a:path)
|
||||
if l:next >= 0
|
||||
if l:curr != l:next
|
||||
execute printf('noautocmd keepalt keepjumps %sbuffer!', bufnr(a:path))
|
||||
endif
|
||||
else
|
||||
execute printf('noautocmd keepalt keepjumps edit! %s', fnameescape(a:path))
|
||||
endif
|
||||
return bufnr('%')
|
||||
endfunction
|
||||
|
@ -1,126 +0,0 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_cmp#VS#Vim#Buffer#import() abort', printf("return map({'get_line_count': '', 'do': '', 'create': '', 'pseudo': '', 'ensure': '', 'load': ''}, \"vital#_cmp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
let s:Do = { -> {} }
|
||||
|
||||
let g:___VS_Vim_Buffer_id = get(g:, '___VS_Vim_Buffer_id', 0)
|
||||
|
||||
"
|
||||
" get_line_count
|
||||
"
|
||||
if exists('*nvim_buf_line_count')
|
||||
function! s:get_line_count(bufnr) abort
|
||||
return nvim_buf_line_count(a:bufnr)
|
||||
endfunction
|
||||
elseif has('patch-8.2.0019')
|
||||
function! s:get_line_count(bufnr) abort
|
||||
return getbufinfo(a:bufnr)[0].linecount
|
||||
endfunction
|
||||
else
|
||||
function! s:get_line_count(bufnr) abort
|
||||
if bufnr('%') == bufnr(a:bufnr)
|
||||
return line('$')
|
||||
endif
|
||||
return len(getbufline(a:bufnr, '^', '$'))
|
||||
endfunction
|
||||
endif
|
||||
|
||||
"
|
||||
" create
|
||||
"
|
||||
function! s:create(...) abort
|
||||
let g:___VS_Vim_Buffer_id += 1
|
||||
let l:bufname = printf('VS.Vim.Buffer: %s: %s',
|
||||
\ g:___VS_Vim_Buffer_id,
|
||||
\ get(a:000, 0, 'VS.Vim.Buffer.Default')
|
||||
\ )
|
||||
return s:load(l:bufname)
|
||||
endfunction
|
||||
|
||||
"
|
||||
" ensure
|
||||
"
|
||||
function! s:ensure(expr) abort
|
||||
if !bufexists(a:expr)
|
||||
if type(a:expr) == type(0)
|
||||
throw printf('VS.Vim.Buffer: `%s` is not valid expr.', a:expr)
|
||||
endif
|
||||
badd `=a:expr`
|
||||
endif
|
||||
return bufnr(a:expr)
|
||||
endfunction
|
||||
|
||||
"
|
||||
" load
|
||||
"
|
||||
if exists('*bufload')
|
||||
function! s:load(expr) abort
|
||||
let l:bufnr = s:ensure(a:expr)
|
||||
if !bufloaded(l:bufnr)
|
||||
call bufload(l:bufnr)
|
||||
endif
|
||||
return l:bufnr
|
||||
endfunction
|
||||
else
|
||||
function! s:load(expr) abort
|
||||
let l:curr_bufnr = bufnr('%')
|
||||
try
|
||||
let l:bufnr = s:ensure(a:expr)
|
||||
execute printf('keepalt keepjumps silent %sbuffer', l:bufnr)
|
||||
catch /.*/
|
||||
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
|
||||
finally
|
||||
execute printf('noautocmd keepalt keepjumps silent %sbuffer', l:curr_bufnr)
|
||||
endtry
|
||||
return l:bufnr
|
||||
endfunction
|
||||
endif
|
||||
|
||||
"
|
||||
" do
|
||||
"
|
||||
function! s:do(bufnr, func) abort
|
||||
let l:curr_bufnr = bufnr('%')
|
||||
if l:curr_bufnr == a:bufnr
|
||||
call a:func()
|
||||
return
|
||||
endif
|
||||
|
||||
try
|
||||
execute printf('noautocmd keepalt keepjumps silent %sbuffer', a:bufnr)
|
||||
call a:func()
|
||||
catch /.*/
|
||||
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
|
||||
finally
|
||||
execute printf('noautocmd keepalt keepjumps silent %sbuffer', l:curr_bufnr)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
"
|
||||
" pseudo
|
||||
"
|
||||
function! s:pseudo(filepath) abort
|
||||
if !filereadable(a:filepath)
|
||||
throw printf('VS.Vim.Buffer: `%s` is not valid filepath.', a:filepath)
|
||||
endif
|
||||
|
||||
" create pseudo buffer
|
||||
let l:bufname = printf('VSVimBufferPseudo://%s', a:filepath)
|
||||
if bufexists(l:bufname)
|
||||
return s:ensure(l:bufname)
|
||||
endif
|
||||
|
||||
let l:bufnr = s:ensure(l:bufname)
|
||||
let l:group = printf('VS_Vim_Buffer_pseudo:%s', l:bufnr)
|
||||
execute printf('augroup %s', l:group)
|
||||
execute printf('autocmd BufReadCmd <buffer=%s> call setline(1, readfile(bufname("%")[20 : -1])) | try | filetype detect | catch /.*/ | endtry | augroup %s | autocmd! | augroup END', l:bufnr, l:group)
|
||||
augroup END
|
||||
return l:bufnr
|
||||
endfunction
|
||||
|
@ -1,21 +0,0 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_cmp#VS#Vim#Option#import() abort', printf("return map({'define': ''}, \"vital#_cmp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
"
|
||||
" define
|
||||
"
|
||||
function! s:define(map) abort
|
||||
let l:old = {}
|
||||
for [l:key, l:value] in items(a:map)
|
||||
let l:old[l:key] = eval(printf('&%s', l:key))
|
||||
execute printf('let &%s = "%s"', l:key, l:value)
|
||||
endfor
|
||||
return { -> s:define(l:old) }
|
||||
endfunction
|
||||
|
@ -1,330 +0,0 @@
|
||||
let s:plugin_name = expand('<sfile>:t:r')
|
||||
let s:vital_base_dir = expand('<sfile>:h')
|
||||
let s:project_root = expand('<sfile>:h:h:h')
|
||||
let s:is_vital_vim = s:plugin_name is# 'vital'
|
||||
|
||||
let s:loaded = {}
|
||||
let s:cache_sid = {}
|
||||
|
||||
function! vital#{s:plugin_name}#new() abort
|
||||
return s:new(s:plugin_name)
|
||||
endfunction
|
||||
|
||||
function! vital#{s:plugin_name}#import(...) abort
|
||||
if !exists('s:V')
|
||||
let s:V = s:new(s:plugin_name)
|
||||
endif
|
||||
return call(s:V.import, a:000, s:V)
|
||||
endfunction
|
||||
|
||||
let s:Vital = {}
|
||||
|
||||
function! s:new(plugin_name) abort
|
||||
let base = deepcopy(s:Vital)
|
||||
let base._plugin_name = a:plugin_name
|
||||
return base
|
||||
endfunction
|
||||
|
||||
function! s:vital_files() abort
|
||||
if !exists('s:vital_files')
|
||||
let s:vital_files = map(
|
||||
\ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(),
|
||||
\ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")')
|
||||
endif
|
||||
return copy(s:vital_files)
|
||||
endfunction
|
||||
let s:Vital.vital_files = function('s:vital_files')
|
||||
|
||||
function! s:import(name, ...) abort dict
|
||||
let target = {}
|
||||
let functions = []
|
||||
for a in a:000
|
||||
if type(a) == type({})
|
||||
let target = a
|
||||
elseif type(a) == type([])
|
||||
let functions = a
|
||||
endif
|
||||
unlet a
|
||||
endfor
|
||||
let module = self._import(a:name)
|
||||
if empty(functions)
|
||||
call extend(target, module, 'keep')
|
||||
else
|
||||
for f in functions
|
||||
if has_key(module, f) && !has_key(target, f)
|
||||
let target[f] = module[f]
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return target
|
||||
endfunction
|
||||
let s:Vital.import = function('s:import')
|
||||
|
||||
function! s:load(...) abort dict
|
||||
for arg in a:000
|
||||
let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg]
|
||||
let target = split(join(as, ''), '\W\+')
|
||||
let dict = self
|
||||
let dict_type = type({})
|
||||
while !empty(target)
|
||||
let ns = remove(target, 0)
|
||||
if !has_key(dict, ns)
|
||||
let dict[ns] = {}
|
||||
endif
|
||||
if type(dict[ns]) == dict_type
|
||||
let dict = dict[ns]
|
||||
else
|
||||
unlet dict
|
||||
break
|
||||
endif
|
||||
endwhile
|
||||
if exists('dict')
|
||||
call extend(dict, self._import(name))
|
||||
endif
|
||||
unlet arg
|
||||
endfor
|
||||
return self
|
||||
endfunction
|
||||
let s:Vital.load = function('s:load')
|
||||
|
||||
function! s:unload() abort dict
|
||||
let s:loaded = {}
|
||||
let s:cache_sid = {}
|
||||
unlet! s:vital_files
|
||||
endfunction
|
||||
let s:Vital.unload = function('s:unload')
|
||||
|
||||
function! s:exists(name) abort dict
|
||||
if a:name !~# '\v^\u\w*%(\.\u\w*)*$'
|
||||
throw 'vital: Invalid module name: ' . a:name
|
||||
endif
|
||||
return s:_module_path(a:name) isnot# ''
|
||||
endfunction
|
||||
let s:Vital.exists = function('s:exists')
|
||||
|
||||
function! s:search(pattern) abort dict
|
||||
let paths = s:_extract_files(a:pattern, self.vital_files())
|
||||
let modules = sort(map(paths, 's:_file2module(v:val)'))
|
||||
return uniq(modules)
|
||||
endfunction
|
||||
let s:Vital.search = function('s:search')
|
||||
|
||||
function! s:plugin_name() abort dict
|
||||
return self._plugin_name
|
||||
endfunction
|
||||
let s:Vital.plugin_name = function('s:plugin_name')
|
||||
|
||||
function! s:_self_vital_files() abort
|
||||
let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name)
|
||||
let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name)
|
||||
let base = builtin . ',' . installed
|
||||
return split(globpath(base, '**/*.vim', 1), "\n")
|
||||
endfunction
|
||||
|
||||
function! s:_global_vital_files() abort
|
||||
let pattern = 'autoload/vital/__*__/**/*.vim'
|
||||
return split(globpath(&runtimepath, pattern, 1), "\n")
|
||||
endfunction
|
||||
|
||||
function! s:_extract_files(pattern, files) abort
|
||||
let tr = {'.': '/', '*': '[^/]*', '**': '.*'}
|
||||
let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g')
|
||||
let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target)
|
||||
return filter(a:files, 'v:val =~# regexp')
|
||||
endfunction
|
||||
|
||||
function! s:_file2module(file) abort
|
||||
let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?')
|
||||
let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$')
|
||||
return join(split(tail, '[\\/]\+'), '.')
|
||||
endfunction
|
||||
|
||||
" @param {string} name e.g. Data.List
|
||||
function! s:_import(name) abort dict
|
||||
if has_key(s:loaded, a:name)
|
||||
return copy(s:loaded[a:name])
|
||||
endif
|
||||
let module = self._get_module(a:name)
|
||||
if has_key(module, '_vital_created')
|
||||
call module._vital_created(module)
|
||||
endif
|
||||
let export_module = filter(copy(module), 'v:key =~# "^\\a"')
|
||||
" Cache module before calling module._vital_loaded() to avoid cyclic
|
||||
" dependences but remove the cache if module._vital_loaded() fails.
|
||||
" let s:loaded[a:name] = export_module
|
||||
let s:loaded[a:name] = export_module
|
||||
if has_key(module, '_vital_loaded')
|
||||
try
|
||||
call module._vital_loaded(vital#{s:plugin_name}#new())
|
||||
catch
|
||||
unlet s:loaded[a:name]
|
||||
throw 'vital: fail to call ._vital_loaded(): ' . v:exception . " from:\n" . s:_format_throwpoint(v:throwpoint)
|
||||
endtry
|
||||
endif
|
||||
return copy(s:loaded[a:name])
|
||||
endfunction
|
||||
let s:Vital._import = function('s:_import')
|
||||
|
||||
function! s:_format_throwpoint(throwpoint) abort
|
||||
let funcs = []
|
||||
let stack = matchstr(a:throwpoint, '^function \zs.*, .\{-} \d\+$')
|
||||
for line in split(stack, '\.\.')
|
||||
let m = matchlist(line, '^\(.\+\)\%(\[\(\d\+\)\]\|, .\{-} \(\d\+\)\)$')
|
||||
if !empty(m)
|
||||
let [name, lnum, lnum2] = m[1:3]
|
||||
if empty(lnum)
|
||||
let lnum = lnum2
|
||||
endif
|
||||
let info = s:_get_func_info(name)
|
||||
if !empty(info)
|
||||
let attrs = empty(info.attrs) ? '' : join([''] + info.attrs)
|
||||
let flnum = info.lnum == 0 ? '' : printf(' Line:%d', info.lnum + lnum)
|
||||
call add(funcs, printf('function %s(...)%s Line:%d (%s%s)',
|
||||
\ info.funcname, attrs, lnum, info.filename, flnum))
|
||||
continue
|
||||
endif
|
||||
endif
|
||||
" fallback when function information cannot be detected
|
||||
call add(funcs, line)
|
||||
endfor
|
||||
return join(funcs, "\n")
|
||||
endfunction
|
||||
|
||||
function! s:_get_func_info(name) abort
|
||||
let name = a:name
|
||||
if a:name =~# '^\d\+$' " is anonymous-function
|
||||
let name = printf('{%s}', a:name)
|
||||
elseif a:name =~# '^<lambda>\d\+$' " is lambda-function
|
||||
let name = printf("{'%s'}", a:name)
|
||||
endif
|
||||
if !exists('*' . name)
|
||||
return {}
|
||||
endif
|
||||
let body = execute(printf('verbose function %s', name))
|
||||
let lines = split(body, "\n")
|
||||
let signature = matchstr(lines[0], '^\s*\zs.*')
|
||||
let [_, file, lnum; __] = matchlist(lines[1],
|
||||
\ '^\t\%(Last set from\|.\{-}:\)\s*\zs\(.\{-}\)\%( \S\+ \(\d\+\)\)\?$')
|
||||
return {
|
||||
\ 'filename': substitute(file, '[/\\]\+', '/', 'g'),
|
||||
\ 'lnum': 0 + lnum,
|
||||
\ 'funcname': a:name,
|
||||
\ 'arguments': split(matchstr(signature, '(\zs.*\ze)'), '\s*,\s*'),
|
||||
\ 'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'),
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
" s:_get_module() returns module object wihch has all script local functions.
|
||||
function! s:_get_module(name) abort dict
|
||||
let funcname = s:_import_func_name(self.plugin_name(), a:name)
|
||||
try
|
||||
return call(funcname, [])
|
||||
catch /^Vim\%((\a\+)\)\?:E117:/
|
||||
return s:_get_builtin_module(a:name)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:_get_builtin_module(name) abort
|
||||
return s:sid2sfuncs(s:_module_sid(a:name))
|
||||
endfunction
|
||||
|
||||
if s:is_vital_vim
|
||||
" For vital.vim, we can use s:_get_builtin_module directly
|
||||
let s:Vital._get_module = function('s:_get_builtin_module')
|
||||
else
|
||||
let s:Vital._get_module = function('s:_get_module')
|
||||
endif
|
||||
|
||||
function! s:_import_func_name(plugin_name, module_name) abort
|
||||
return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name))
|
||||
endfunction
|
||||
|
||||
function! s:_module_sid(name) abort
|
||||
let path = s:_module_path(a:name)
|
||||
if !filereadable(path)
|
||||
throw 'vital: module not found: ' . a:name
|
||||
endif
|
||||
let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name)
|
||||
let base = join([vital_dir, ''], '[/\\]\+')
|
||||
let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g')
|
||||
let sid = s:_sid(path, p)
|
||||
if !sid
|
||||
call s:_source(path)
|
||||
let sid = s:_sid(path, p)
|
||||
if !sid
|
||||
throw printf('vital: cannot get <SID> from path: %s', path)
|
||||
endif
|
||||
endif
|
||||
return sid
|
||||
endfunction
|
||||
|
||||
function! s:_module_path(name) abort
|
||||
return get(s:_extract_files(a:name, s:vital_files()), 0, '')
|
||||
endfunction
|
||||
|
||||
function! s:_module_sid_base_dir() abort
|
||||
return s:is_vital_vim ? &rtp : s:project_root
|
||||
endfunction
|
||||
|
||||
function! s:_dot_to_sharp(name) abort
|
||||
return substitute(a:name, '\.', '#', 'g')
|
||||
endfunction
|
||||
|
||||
function! s:_source(path) abort
|
||||
execute 'source' fnameescape(a:path)
|
||||
endfunction
|
||||
|
||||
" @vimlint(EVL102, 1, l:_)
|
||||
" @vimlint(EVL102, 1, l:__)
|
||||
function! s:_sid(path, filter_pattern) abort
|
||||
let unified_path = s:_unify_path(a:path)
|
||||
if has_key(s:cache_sid, unified_path)
|
||||
return s:cache_sid[unified_path]
|
||||
endif
|
||||
for line in filter(split(execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern')
|
||||
let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$')
|
||||
if s:_unify_path(path) is# unified_path
|
||||
let s:cache_sid[unified_path] = sid
|
||||
return s:cache_sid[unified_path]
|
||||
endif
|
||||
endfor
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
if filereadable(expand('<sfile>:r') . '.VIM') " is case-insensitive or not
|
||||
let s:_unify_path_cache = {}
|
||||
" resolve() is slow, so we cache results.
|
||||
" Note: On windows, vim can't expand path names from 8.3 formats.
|
||||
" So if getting full path via <sfile> and $HOME was set as 8.3 format,
|
||||
" vital load duplicated scripts. Below's :~ avoid this issue.
|
||||
function! s:_unify_path(path) abort
|
||||
if has_key(s:_unify_path_cache, a:path)
|
||||
return s:_unify_path_cache[a:path]
|
||||
endif
|
||||
let value = tolower(fnamemodify(resolve(fnamemodify(
|
||||
\ a:path, ':p')), ':~:gs?[\\/]?/?'))
|
||||
let s:_unify_path_cache[a:path] = value
|
||||
return value
|
||||
endfunction
|
||||
else
|
||||
function! s:_unify_path(path) abort
|
||||
return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?'))
|
||||
endfunction
|
||||
endif
|
||||
|
||||
" copied and modified from Vim.ScriptLocal
|
||||
let s:SNR = join(map(range(len("\<SNR>")), '"[\\x" . printf("%0x", char2nr("\<SNR>"[v:val])) . "]"'), '')
|
||||
function! s:sid2sfuncs(sid) abort
|
||||
let fs = split(execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n")
|
||||
let r = {}
|
||||
let pattern = printf('\m^function\s<SNR>%d_\zs\w\{-}\ze(', a:sid)
|
||||
for fname in map(fs, 'matchstr(v:val, pattern)')
|
||||
let r[fname] = function(s:_sfuncname(a:sid, fname))
|
||||
endfor
|
||||
return r
|
||||
endfunction
|
||||
|
||||
"" Return funcname of script local functions with SID
|
||||
function! s:_sfuncname(sid, funcname) abort
|
||||
return printf('<SNR>%s_%s', a:sid, a:funcname)
|
||||
endfunction
|
@ -1,4 +0,0 @@
|
||||
cmp
|
||||
2755f0c8fbd3442bcb7f567832e4d1455b57f9a2
|
||||
|
||||
VS.LSP.TextEdit
|
696
bundle/nvim-cmp/doc/cmp.txt
Normal file
696
bundle/nvim-cmp/doc/cmp.txt
Normal file
@ -0,0 +1,696 @@
|
||||
*nvim-cmp* *cmp*
|
||||
|
||||
A completion plugin for neovim coded in Lua.
|
||||
|
||||
==============================================================================
|
||||
CONTENTS *cmp-contents*
|
||||
|
||||
Abstract |cmp-abstract|
|
||||
Concept |cmp-concept|
|
||||
Usage |cmp-usage|
|
||||
Function |cmp-function|
|
||||
Mapping |cmp-mapping|
|
||||
Command |cmp-command|
|
||||
Highlight |cmp-highlight|
|
||||
Autocmd |cmp-autocmd|
|
||||
Config |cmp-config|
|
||||
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.
|
||||
- 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
|
||||
- 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.
|
||||
>
|
||||
call plug#begin(s:plug_dir)
|
||||
Plug 'neovim/nvim-lspconfig'
|
||||
Plug 'hrsh7th/cmp-nvim-lsp'
|
||||
Plug 'hrsh7th/cmp-buffer'
|
||||
Plug 'hrsh7th/cmp-path'
|
||||
Plug 'hrsh7th/cmp-cmdline'
|
||||
Plug 'hrsh7th/nvim-cmp'
|
||||
|
||||
" For vsnip users.
|
||||
Plug 'hrsh7th/cmp-vsnip'
|
||||
Plug 'hrsh7th/vim-vsnip'
|
||||
|
||||
" For luasnip users.
|
||||
" Plug 'L3MON4D3/LuaSnip'
|
||||
" Plug 'saadparwaiz1/cmp_luasnip'
|
||||
|
||||
" For snippy users.
|
||||
" Plug 'dcampos/nvim-snippy'
|
||||
" Plug 'dcampos/cmp-snippy'
|
||||
|
||||
" For ultisnips users.
|
||||
" Plug 'SirVer/ultisnips'
|
||||
" Plug 'quangnguyen30192/cmp-nvim-ultisnips'
|
||||
|
||||
call plug#end()
|
||||
|
||||
set completeopt=menu,menuone,noselect
|
||||
|
||||
lua <<EOF
|
||||
local cmp = require'cmp'
|
||||
|
||||
-- Global setup.
|
||||
cmp.setup({
|
||||
snippet = {
|
||||
expand = function(args)
|
||||
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
|
||||
-- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
|
||||
-- require'snippy'.expand_snippet(args.body) -- For `snippy` users.
|
||||
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
|
||||
end,
|
||||
},
|
||||
mapping = {
|
||||
['<C-d>'] = cmp.mapping(cmp.mapping.scroll_docs(-4), { 'i', 'c' }),
|
||||
['<C-f>'] = cmp.mapping(cmp.mapping.scroll_docs(4), { 'i', 'c' }),
|
||||
['<C-Space>'] = cmp.mapping(cmp.mapping.complete(), { 'i', 'c' }),
|
||||
['<C-e>'] = 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.
|
||||
['<CR>'] = cmp.mapping.confirm({ select = true }),
|
||||
},
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'nvim_lsp' },
|
||||
{ name = 'vsnip' }, -- For vsnip users.
|
||||
-- { name = 'luasnip' }, -- For luasnip users.
|
||||
-- { name = 'snippy' }, -- For snippy users.
|
||||
-- { name = 'ultisnips' }, -- For ultisnips users.
|
||||
}, {
|
||||
{ name = 'buffer' },
|
||||
})
|
||||
})
|
||||
|
||||
-- `/` cmdline setup.
|
||||
cmp.setup.cmdline('/', {
|
||||
sources = {
|
||||
{ name = 'buffer' }
|
||||
}
|
||||
})
|
||||
|
||||
-- `:` cmdline setup.
|
||||
cmp.setup.cmdline(':', {
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'path' }
|
||||
}, {
|
||||
{ name = 'cmdline' }
|
||||
})
|
||||
})
|
||||
|
||||
-- Setup lspconfig.
|
||||
local capabilities = require('cmp_nvim_lsp').update_capabilities(vim.lsp.protocol.make_client_capabilities())
|
||||
require('lspconfig')[%YOUR_LSP_SERVER%].setup {
|
||||
capabilities = capabilities
|
||||
}
|
||||
EOF
|
||||
<
|
||||
|
||||
|
||||
==============================================================================
|
||||
Function *cmp-function*
|
||||
|
||||
NOTE: You can call these functions in mapping via `<Cmd>lua require('cmp').complete()<CR>`.
|
||||
|
||||
*cmp.setup* (config: cmp.ConfigSchema)
|
||||
Setup global configuration. See configuration option.
|
||||
|
||||
*cmp.setup.filetype* (filetype: string, config: cmp.ConfigSchema)
|
||||
Setup filetype configuration to the specific filetype.
|
||||
|
||||
*cmp.setup.buffer* (config: cmp.ConfigSchema)
|
||||
Setup buffer configuration to 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.
|
||||
|
||||
*cmp.visible* ()
|
||||
Return the completion menu is visible or not.
|
||||
|
||||
*cmp.get_entries* ()
|
||||
Return current all entries.
|
||||
|
||||
*cmp.get_selected_entry* ()
|
||||
Return current selected entry. (contains preselected)
|
||||
|
||||
*cmp.get_active_entry* ()
|
||||
Return current selected entry. (without preselected)
|
||||
|
||||
*cmp.close* ()
|
||||
Just close the completion menu.
|
||||
|
||||
*cmp.abort* ()
|
||||
Closes the completion menu and restore the current line to the state when it was started current completion.
|
||||
|
||||
*cmp.select_next_item* (option: { behavior = cmp.SelectBehavior })
|
||||
Select next item.
|
||||
|
||||
*cmp.select_prev_item* (option: { behavior = cmp.SelectBehavior })*
|
||||
Select prev item.
|
||||
|
||||
*cmp.scroll_docs* (delta: number)
|
||||
Scroll docs if it visible.
|
||||
|
||||
*cmp.complete* (option: { reason = cmp.ContextReason, config = cmp.ConfigSchema })
|
||||
Invoke completion.
|
||||
|
||||
The following configurations defines the key mapping to invoke only snippet completion.
|
||||
>
|
||||
cmp.setup {
|
||||
mapping = {
|
||||
['<C-s>'] = cmp.mapping.complete({
|
||||
config = {
|
||||
sources = {
|
||||
{ name = 'vsnip' }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
< >
|
||||
inoremap <C-S> <Cmd>lua require('cmp').complete({ config = { sources = { { name = 'vsnip' } } } })<CR>
|
||||
<
|
||||
NOTE: The `config` means a temporary setting, but the `config.mapping` remains permanent.
|
||||
|
||||
*cmp.complete_common_string* ()
|
||||
Complete common string as like as shell completion behavior.
|
||||
>
|
||||
cmp.setup {
|
||||
mapping = {
|
||||
['<C-l>'] = cmp.mapping(function(fallback)
|
||||
if cmp.visible() then
|
||||
return cmp.complete_common_string()
|
||||
end
|
||||
fallback()
|
||||
end, { 'i', 'c' }),
|
||||
}
|
||||
}
|
||||
<
|
||||
*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.
|
||||
|
||||
*cmp.event:on* ('%EVENT_NAME%, callback)
|
||||
Subscribe nvim-cmp's events below.
|
||||
|
||||
- `complete_done`: emit after current completion is done.
|
||||
- `confirm_done`: emit after confirmation is done.
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
Mapping *cmp-mapping*
|
||||
|
||||
The 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.
|
||||
The `fallback` function can be used to call an existing mapping.
|
||||
|
||||
For example, typical pair-wise plugins automatically defines a mapping for `<CR>` or `(`.
|
||||
The nvim-cmp will overwrite it but you can fallback to the original mapping via invoking the `fallback` function.
|
||||
>
|
||||
cmp.setup {
|
||||
mapping = {
|
||||
['<CR>'] = 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.
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
<
|
||||
And you can specify the mapping modes.
|
||||
>
|
||||
cmp.setup {
|
||||
mapping = {
|
||||
['<CR>'] = cmp.mapping(your_mapping_function, { 'i', 'c' })
|
||||
}
|
||||
}
|
||||
<
|
||||
And you can specify the different mapping function for each modes.
|
||||
>
|
||||
cmp.setup {
|
||||
mapping = {
|
||||
['<CR>'] = cmp.mapping({
|
||||
i = your_mapping_function_a,
|
||||
c = your_mapping_function_b,
|
||||
})
|
||||
}
|
||||
}
|
||||
<
|
||||
You can also use built-in mapping helpers.
|
||||
|
||||
*cmp.mapping.close* ()
|
||||
Same as |cmp.close|
|
||||
|
||||
*cmp.mapping.abort* ()
|
||||
Same as |cmp.abort|
|
||||
|
||||
*cmp.mapping.select_next_item* (option: { behavior = cmp.SelectBehavior })
|
||||
Same as |cmp.select_next_item|
|
||||
|
||||
*cmp.mapping.select_prev_item* (option: { behavior = cmp.SelectBehavior })
|
||||
Same as |cmp.select_prev_item|
|
||||
|
||||
*cmp.mapping.scroll_docs* (delta: number)
|
||||
Same as |cmp.scroll_docs|
|
||||
|
||||
*cmp.mapping.complete* (option: cmp.CompleteParams)
|
||||
Same as |cmp.complete|
|
||||
|
||||
*cmp.mapping.complete_common_string* ()
|
||||
Same as |cmp.complete_common_string|
|
||||
|
||||
*cmp.mapping.confirm* (option: cmp.ConfirmOption)
|
||||
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.
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
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.
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
Highlight *cmp-highlight*
|
||||
|
||||
*CmpItemAbbr*
|
||||
The abbr field's highlight group.
|
||||
|
||||
*CmpItemAbbrDeprecated*
|
||||
The abbr field's highlight group that only used for deprecated item.
|
||||
|
||||
*CmpItemAbbrMatch*
|
||||
The matched character's highlight group.
|
||||
|
||||
*CmpItemAbbrMatchFuzzy*
|
||||
The fuzzy matched character's highlight group.
|
||||
|
||||
*CmpItemKind*
|
||||
The kind field's highlight group.
|
||||
|
||||
*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 CmpItemKindMethod guibg=NONE guifg=Orange
|
||||
<
|
||||
*CmpItemMenu*
|
||||
The menu field's highlight group.
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
Autocmd *cmp-autocmd*
|
||||
|
||||
You can create custom autocommands for certain nvim-cmp events by defining
|
||||
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.
|
||||
|
||||
*cmp-config.enabled*
|
||||
enabled~
|
||||
`boolean | fun(): boolean`
|
||||
You can control nvim-cmp should work or not via this option.
|
||||
|
||||
*cmp-config.preselect*
|
||||
preselect~
|
||||
`cmp.PreselectMode`
|
||||
|
||||
1. `cmp.PreselectMode.Item`
|
||||
nvim-cmp will pre-select the item that the source specified.
|
||||
2. `cmp.PreselectMode.None`
|
||||
nvim-cmp wouldn't pre-select any item.
|
||||
|
||||
*cmp-config.mapping*
|
||||
mapping~
|
||||
`table<string, fun(fallback: function)`
|
||||
See |cmp-mapping| section.
|
||||
|
||||
*cmp-config.snippet.expand*
|
||||
snippet.expand~
|
||||
`fun(option: cmp.SnippetExpansionParams)`
|
||||
The snippet expansion function. You must integrate your snippet engine plugin via this.
|
||||
|
||||
*cmp-config.completion.keyword_length*
|
||||
completion.keyword_length~
|
||||
`number`
|
||||
The number of characters needed to trigger auto-completion.
|
||||
|
||||
*cmp-config.completion.keyword_pattern*
|
||||
completion.keyword_pattern~
|
||||
`string`
|
||||
The default 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.
|
||||
|
||||
*cmp-config.completion.completeopt*
|
||||
completion.completeopt~
|
||||
`string`
|
||||
The vim's completeopt like setting. See 'completeopt'.
|
||||
Besically, You don't need to modify this.
|
||||
|
||||
*cmp-config.formatting.fields*
|
||||
formatting.fields~
|
||||
`cmp.ItemField[]`
|
||||
The array of completion menu field to specify the order of them.
|
||||
|
||||
*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`.
|
||||
|
||||
*cmp-config.matching.disallow_fuzzy_matching*
|
||||
matching.disallow_fuzzy_matching~
|
||||
`boolean`
|
||||
Specify disallow or allow fuzzy matching.
|
||||
|
||||
*cmp-config.matching.disallow_partial_matching*
|
||||
matching.disallow_partial_matching~
|
||||
`boolean`
|
||||
Specify disallow or allow partial matching.
|
||||
|
||||
*cmp-config.matching.disallow_prefix_unmatching*
|
||||
matching.disallow_prefix_unmatching~
|
||||
`boolean`
|
||||
Specify disallow or 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`.
|
||||
That is, the final priority is calculated by the following formula:
|
||||
>
|
||||
final_score = orig_score + ((#sources - (source_index - 1)) * sorting.priority_weight)
|
||||
<
|
||||
*cmp-config.sorting.comparators*
|
||||
sorting.comparators~
|
||||
`(fun(entry1: cmp.Entry, entry2: cmp.Entry): boolean | nil)[]`
|
||||
The function to customize the sorting behavior.
|
||||
You can use built-in comparators via `cmp.config.compare.*`.
|
||||
|
||||
*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.
|
||||
|
||||
*cmp-config.sources[n].name*
|
||||
sources[n].name~
|
||||
`string`
|
||||
The source name.
|
||||
|
||||
*cmp-config.sources[n].option*
|
||||
sources[n].option~
|
||||
`table`
|
||||
The source specific custom option that defined by the source.
|
||||
|
||||
*cmp-config.sources[n].keyword_length*
|
||||
sources[n].keyword_length~
|
||||
`number`
|
||||
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.
|
||||
|
||||
*cmp-config.sources[n].trigger_characters*
|
||||
sources[n].trigger_characters~
|
||||
`string[]`
|
||||
The 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.
|
||||
|
||||
*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.
|
||||
>
|
||||
cmp.setup {
|
||||
sources = {
|
||||
{ name = 'nvim_lsp', group_index = 1 },
|
||||
{ name = 'buffer', group_index = 2 },
|
||||
}
|
||||
}
|
||||
<
|
||||
You can specify this via the built-in configuration helper like this.
|
||||
>
|
||||
cmp.setup {
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'nvim_lsp' },
|
||||
}, {
|
||||
{ name = 'buffer' },
|
||||
})
|
||||
}
|
||||
<
|
||||
*cmp-config.view*
|
||||
view~
|
||||
`{ entries: cmp.EntriesConfig|string }`
|
||||
Specify the view class to customize appearance.
|
||||
Currently, the possible configurations are:
|
||||
|
||||
*cmp-config.experimental.ghost_text*
|
||||
experimental.ghost_text~
|
||||
`boolean | { hl_group = string }`
|
||||
The boolean value to enable or disable the ghost_text feature.
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
Develop *cmp-develop*
|
||||
|
||||
Create custom source~
|
||||
|
||||
NOTE:
|
||||
1. The `complete` method is required. Others can be ommited.
|
||||
2. The `callback` argument 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.
|
||||
|
||||
You can create custom source like the following example.
|
||||
|
||||
>
|
||||
local source = {}
|
||||
|
||||
---Return this source is available in current context or not. (Optional)
|
||||
---@return boolean
|
||||
function source:is_available()
|
||||
return true
|
||||
end
|
||||
|
||||
---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 string
|
||||
function source:get_keyword_pattern()
|
||||
return [[\k\+]]
|
||||
end
|
||||
|
||||
---Return trigger characters for triggering completion. (Optional)
|
||||
function source:get_trigger_characters()
|
||||
return { '.' }
|
||||
end
|
||||
|
||||
---Invoke completion. (Required)
|
||||
---@param params cmp.SourceCompletionApiParams
|
||||
---@param callback fun(response: lsp.CompletionResponse|nil)
|
||||
function source:complete(params, callback)
|
||||
callback({
|
||||
{ label = 'January' },
|
||||
{ label = 'February' },
|
||||
{ label = 'March' },
|
||||
{ label = 'April' },
|
||||
{ label = 'May' },
|
||||
{ label = 'June' },
|
||||
{ label = 'July' },
|
||||
{ label = 'August' },
|
||||
{ label = 'September' },
|
||||
{ label = 'October' },
|
||||
{ label = 'November' },
|
||||
{ label = 'December' },
|
||||
})
|
||||
end
|
||||
|
||||
---Resolve completion item. (Optional)
|
||||
---@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.
|
||||
---@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())
|
||||
<
|
||||
|
||||
|
||||
==============================================================================
|
||||
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.
|
||||
|
||||
You can disable the `preselect` feature like the following.
|
||||
>
|
||||
cmp.setup {
|
||||
preselect = cmp.PreselectMode.None
|
||||
}
|
||||
<
|
||||
|
||||
|
||||
How to disable auto-completion?~
|
||||
How to use nvim-cmp as like omnifunc?~
|
||||
|
||||
You can disable auto-completion like this.
|
||||
>
|
||||
cmp.setup {
|
||||
...
|
||||
completion = {
|
||||
autocomplete = false
|
||||
}
|
||||
...
|
||||
}
|
||||
<
|
||||
And you can invoke completion manually.
|
||||
>
|
||||
inoremap <C-x><C-o> <Cmd>lua require('cmp').complete()<CR>
|
||||
<
|
||||
|
||||
How to disable nvim-cmp on the specific buffer?~
|
||||
How to setup on the specific buffer?~
|
||||
|
||||
You can setup buffer specific configuration like this.
|
||||
>
|
||||
cmp.setup.filetype({ 'markdown', 'help' }, {
|
||||
sources = {
|
||||
{ name = 'path' },
|
||||
{ name = 'buffer' },
|
||||
}
|
||||
})
|
||||
<
|
||||
|
||||
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.
|
||||
|
||||
Fortunately, the copilot.vim has the feature that disables the fallback mechanism.
|
||||
>
|
||||
let g:copilot_no_tab_map = v:true
|
||||
imap <expr> <Plug>(vimrc:copilot-dummy-map) copilot#Accept("\<Tab>")
|
||||
<
|
||||
You can manage copilot.vim's accept feature with nvim-cmp' key-mapping configuration.
|
||||
>
|
||||
cmp.setup {
|
||||
mapping = {
|
||||
['<C-g>'] = cmp.mapping(function(fallback)
|
||||
vim.api.nvim_feedkeys(vim.fn['copilot#Accept'](vim.api.nvim_replace_termcodes('<Tab>', true, true, true)), 'n', true)
|
||||
end)
|
||||
},
|
||||
experimental = {
|
||||
ghost_text = false -- this feature conflict to the copilot.vim's preview.
|
||||
}
|
||||
}
|
||||
<
|
||||
|
||||
|
||||
How to customize menu appearance?~
|
||||
|
||||
You can see the nvim-cmp wiki (https://github.com/hrsh7th/nvim-cmp/wiki).
|
||||
|
||||
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=4:et:ft=help:norl:
|
||||
|
@ -17,13 +17,19 @@ config.global = require('cmp.config.default')()
|
||||
---@type table<number, cmp.ConfigSchema>
|
||||
config.buffers = {}
|
||||
|
||||
---@type table<string, cmp.ConfigSchema>
|
||||
config.filetypes = {}
|
||||
|
||||
---@type table<string, cmp.ConfigSchema>
|
||||
config.cmdline = {}
|
||||
|
||||
---@type cmp.ConfigSchema
|
||||
config.onetime = {}
|
||||
|
||||
---Set configuration for global.
|
||||
---@param c cmp.ConfigSchema
|
||||
config.set_global = function(c)
|
||||
config.global = misc.merge(c, config.global)
|
||||
config.global = misc.merge(config.normalize(c), config.normalize(config.global))
|
||||
config.global.revision = config.global.revision or 1
|
||||
config.global.revision = config.global.revision + 1
|
||||
end
|
||||
@ -33,31 +39,82 @@ end
|
||||
---@param bufnr number|nil
|
||||
config.set_buffer = function(c, bufnr)
|
||||
local revision = (config.buffers[bufnr] or {}).revision or 1
|
||||
config.buffers[bufnr] = c
|
||||
config.buffers[bufnr] = c or {}
|
||||
config.buffers[bufnr].revision = revision + 1
|
||||
end
|
||||
|
||||
---Set configuration for filetype
|
||||
---@param c cmp.ConfigSchema
|
||||
---@param filetypes string[]|string
|
||||
config.set_filetype = function(c, filetypes)
|
||||
for _, filetype in ipairs(type(filetypes) == 'table' and filetypes or { filetypes }) do
|
||||
local revision = (config.filetypes[filetype] or {}).revision or 1
|
||||
config.filetypes[filetype] = c or {}
|
||||
config.filetypes[filetype].revision = revision + 1
|
||||
end
|
||||
end
|
||||
|
||||
---Set configuration for cmdline
|
||||
config.set_cmdline = function(c, type)
|
||||
local revision = (config.cmdline[type] or {}).revision or 1
|
||||
config.cmdline[type] = c
|
||||
config.cmdline[type].revision = revision + 1
|
||||
---@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
|
||||
end
|
||||
|
||||
---Set configuration as oneshot completion.
|
||||
---@param c cmp.ConfigSchema
|
||||
config.set_onetime = function(c)
|
||||
local revision = (config.onetime or {}).revision or 1
|
||||
config.onetime = c or {}
|
||||
config.onetime.revision = revision + 1
|
||||
end
|
||||
|
||||
---@return cmp.ConfigSchema
|
||||
config.get = function()
|
||||
local global = config.global
|
||||
if api.is_cmdline_mode() then
|
||||
local type = vim.fn.getcmdtype()
|
||||
local cmdline = config.cmdline[type] or { revision = 1, sources = {} }
|
||||
return config.cache:ensure({ 'get_cmdline', type, global.revision or 0, cmdline.revision or 0 }, function()
|
||||
return misc.merge(config.normalize(cmdline), config.normalize(global))
|
||||
local global_config = config.global
|
||||
if config.onetime.sources then
|
||||
local onetime_config = config.onetime
|
||||
return config.cache:ensure({
|
||||
'get',
|
||||
'onetime',
|
||||
global_config.revision or 0,
|
||||
onetime_config.revision or 0,
|
||||
}, function()
|
||||
return misc.merge(config.normalize(onetime_config), config.normalize(global_config))
|
||||
end)
|
||||
elseif api.is_cmdline_mode() then
|
||||
local cmdtype = vim.fn.getcmdtype()
|
||||
local cmdline_config = config.cmdline[cmdtype] or { revision = 1, sources = {} }
|
||||
return config.cache:ensure({
|
||||
'get',
|
||||
'cmdline',
|
||||
global_config.revision or 0,
|
||||
cmdtype,
|
||||
cmdline_config.revision or 0,
|
||||
}, function()
|
||||
return misc.merge(config.normalize(cmdline_config), config.normalize(global_config))
|
||||
end)
|
||||
else
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local buffer = config.buffers[bufnr] or { revision = 1 }
|
||||
return config.cache:ensure({ 'get_buffer', bufnr, global.revision or 0, buffer.revision or 0 }, function()
|
||||
return misc.merge(config.normalize(buffer), config.normalize(global))
|
||||
local filetype = vim.api.nvim_buf_get_option(bufnr, 'filetype')
|
||||
local buffer_config = config.buffers[bufnr] or { revision = 1 }
|
||||
local filetype_config = config.filetypes[filetype] or { revision = 1 }
|
||||
return config.cache:ensure({
|
||||
'get',
|
||||
'default',
|
||||
global_config.revision or 0,
|
||||
filetype,
|
||||
filetype_config.revision or 0,
|
||||
bufnr,
|
||||
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))
|
||||
return c
|
||||
end)
|
||||
end
|
||||
end
|
||||
@ -78,19 +135,31 @@ config.get_source_config = function(name)
|
||||
local c = config.get()
|
||||
for _, s in ipairs(c.sources) do
|
||||
if s.name == name then
|
||||
if type(s.opts) ~= 'table' then
|
||||
s.opts = {}
|
||||
end
|
||||
return s
|
||||
end
|
||||
end
|
||||
return nil
|
||||
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
|
||||
return false
|
||||
end
|
||||
|
||||
---Normalize mapping key
|
||||
---@param c cmp.ConfigSchema
|
||||
---@return cmp.ConfigSchema
|
||||
config.normalize = function(c)
|
||||
-- make sure c is not 'nil'
|
||||
c = c == nil and {} or c
|
||||
|
||||
if c.mapping then
|
||||
local normalized = {}
|
||||
for k, v in pairs(c.mapping) do
|
||||
@ -98,6 +167,39 @@ config.normalize = function(c)
|
||||
end
|
||||
c.mapping = normalized
|
||||
end
|
||||
|
||||
if c.experimental and c.experimental.native_menu then
|
||||
vim.api.nvim_echo({
|
||||
{ '[nvim-cmp] ', 'Normal' },
|
||||
{ 'experimental.native_menu', 'WarningMsg' },
|
||||
{ ' is deprecated.\n', 'Normal' },
|
||||
{ '[nvim-cmp] Please use ', 'Normal' },
|
||||
{ 'view.entries = "native"', 'WarningMsg' },
|
||||
{ ' instead.', 'Normal' },
|
||||
}, true, {})
|
||||
|
||||
c.view = c.view or {}
|
||||
c.view.entries = c.view.entries or 'native'
|
||||
end
|
||||
|
||||
if c.sources then
|
||||
for _, s in ipairs(c.sources) do
|
||||
if s.opts and not s.option then
|
||||
s.option = s.opts
|
||||
s.opts = nil
|
||||
vim.api.nvim_echo({
|
||||
{ '[nvim-cmp] ', 'Normal' },
|
||||
{ 'sources[number].opts', 'WarningMsg' },
|
||||
{ ' is deprecated.\n', 'Normal' },
|
||||
{ '[nvim-cmp] Please use ', 'Normal' },
|
||||
{ 'sources[number].option', 'WarningMsg' },
|
||||
{ ' instead.', 'Normal' },
|
||||
}, true, {})
|
||||
end
|
||||
s.option = s.option or {}
|
||||
end
|
||||
end
|
||||
|
||||
return c
|
||||
end
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
local types = require('cmp.types')
|
||||
local cache = require('cmp.utils.cache')
|
||||
local misc = require('cmp.utils.misc')
|
||||
|
||||
local compare = {}
|
||||
@ -100,4 +101,135 @@ compare.order = function(entry1, entry2)
|
||||
end
|
||||
end
|
||||
|
||||
-- locality
|
||||
compare.locality = setmetatable({
|
||||
lines_count = 10,
|
||||
lines_cache = cache.new(),
|
||||
locality_map = {},
|
||||
update = function(self)
|
||||
local config = require('cmp').get_config()
|
||||
if not vim.tbl_contains(config.sorting.comparators, compare.scopes) then
|
||||
return
|
||||
end
|
||||
|
||||
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 max = vim.api.nvim_buf_line_count(buf)
|
||||
|
||||
if self.lines_cache:get('buf') ~= buf then
|
||||
self.lines_cache:clear()
|
||||
self.lines_cache:set('buf', buf)
|
||||
end
|
||||
|
||||
self.locality_map = {}
|
||||
for i = math.max(0, cursor_row - self.lines_count), math.min(max, cursor_row + self.lines_count) do
|
||||
local is_above = i < cursor_row
|
||||
local buffer = vim.api.nvim_buf_get_lines(buf, i, i + 1, false)[1] or ''
|
||||
local locality_map = self.lines_cache:ensure({ 'line', buffer }, function()
|
||||
local locality_map = {}
|
||||
local regexp = vim.regex(config.completion.keyword_pattern)
|
||||
while buffer ~= '' do
|
||||
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)
|
||||
locality_map[w] = math.min(locality_map[w] or math.huge, d)
|
||||
buffer = string.sub(buffer, e + 1)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return locality_map
|
||||
end)
|
||||
for w, d in pairs(locality_map) do
|
||||
self.locality_map[w] = math.min(self.locality_map[w] or d, math.abs(i - cursor_row))
|
||||
end
|
||||
end
|
||||
end
|
||||
}, {
|
||||
__call = function(self, entry1, entry2)
|
||||
local local1 = self.locality_map[entry1:get_word()]
|
||||
local local2 = self.locality_map[entry2:get_word()]
|
||||
if local1 ~= local2 then
|
||||
if local1 == nil then
|
||||
return false
|
||||
end
|
||||
if local2 == nil then
|
||||
return true
|
||||
end
|
||||
return local1 < local2
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- scopes
|
||||
compare.scopes = setmetatable({
|
||||
scopes_map = {},
|
||||
update = function(self)
|
||||
local config = require('cmp').get_config()
|
||||
if not vim.tbl_contains(config.sorting.comparators, compare.scopes) then
|
||||
return
|
||||
end
|
||||
|
||||
local ok, locals = pcall(require, 'nvim-treesitter.locals')
|
||||
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
|
||||
for _, scope in ipairs(locals.get_scopes(buf)) do
|
||||
if scope:start() <= cursor_row and cursor_row <= scope:end_() then
|
||||
if not cursor_scope then
|
||||
cursor_scope = scope
|
||||
else
|
||||
if cursor_scope:start() <= scope:start() and scope:end_() <= cursor_scope:end_() then
|
||||
cursor_scope = scope
|
||||
end
|
||||
end
|
||||
elseif cursor_scope and cursor_scope:end_() <= scope:start() then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Definitions.
|
||||
local definitions = locals.get_definitions_lookup_table(buf)
|
||||
|
||||
-- Narrow definitions.
|
||||
local depth = 0
|
||||
for scope in locals.iter_scope_tree(cursor_scope, buf) do
|
||||
local s, e = scope:start(), scope:end_()
|
||||
|
||||
-- Check scope's direct child.
|
||||
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]
|
||||
if not self.scopes_map[text] then
|
||||
self.scopes_map[text] = depth
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
depth = depth + 1
|
||||
end
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
__call = function(self, entry1, entry2)
|
||||
local local1 = self.scopes_map[entry1:get_word()]
|
||||
local local2 = self.scopes_map[entry2:get_word()]
|
||||
if local1 ~= local2 then
|
||||
if local1 == nil then
|
||||
return false
|
||||
end
|
||||
if local2 == nil then
|
||||
return true
|
||||
end
|
||||
return local1 < local2
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return compare
|
||||
|
65
bundle/nvim-cmp/lua/cmp/config/context.lua
Normal file
65
bundle/nvim-cmp/lua/cmp/config/context.lua
Normal file
@ -0,0 +1,65 @@
|
||||
local context = {}
|
||||
|
||||
---Check if cursor is in syntax group
|
||||
---@param group 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
|
||||
syn_id = vim.fn.synIDtrans(syn_id) -- Resolve :highlight links
|
||||
if vim.fn.synIDattr(syn_id, 'name') == group then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Check if cursor is in treesitter capture
|
||||
---@param capture 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
|
||||
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())
|
||||
end
|
||||
end
|
||||
end, true)
|
||||
|
||||
return vim.tbl_contains(node_types, capture)
|
||||
end
|
||||
|
||||
return context
|
@ -1,5 +1,6 @@
|
||||
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
|
||||
@ -8,58 +9,15 @@ local WIDE_HEIGHT = 40
|
||||
return function()
|
||||
return {
|
||||
enabled = function()
|
||||
return vim.api.nvim_buf_get_option(0, 'buftype') ~= 'prompt'
|
||||
local disabled = false
|
||||
disabled = disabled or (vim.api.nvim_buf_get_option(0, 'buftype') == 'prompt')
|
||||
disabled = disabled or (vim.fn.reg_recording() ~= '')
|
||||
disabled = disabled or (vim.fn.reg_executing() ~= '')
|
||||
return not disabled
|
||||
end,
|
||||
completion = {
|
||||
autocomplete = {
|
||||
types.cmp.TriggerEvent.TextChanged,
|
||||
},
|
||||
completeopt = 'menu,menuone,noselect',
|
||||
keyword_pattern = [[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]],
|
||||
keyword_length = 1,
|
||||
get_trigger_characters = function(trigger_characters)
|
||||
return trigger_characters
|
||||
end,
|
||||
},
|
||||
|
||||
snippet = {
|
||||
expand = function()
|
||||
error('snippet engine is not configured.')
|
||||
end,
|
||||
},
|
||||
|
||||
preselect = types.cmp.PreselectMode.Item,
|
||||
|
||||
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)
|
||||
return commit_characters
|
||||
end,
|
||||
},
|
||||
|
||||
sorting = {
|
||||
priority_weight = 2,
|
||||
comparators = {
|
||||
compare.offset,
|
||||
compare.exact,
|
||||
compare.score,
|
||||
compare.recently_used,
|
||||
compare.kind,
|
||||
compare.sort_text,
|
||||
compare.length,
|
||||
compare.order,
|
||||
},
|
||||
},
|
||||
|
||||
event = {},
|
||||
|
||||
mapping = {
|
||||
['<Down>'] = mapping({
|
||||
i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }),
|
||||
@ -80,30 +38,38 @@ return function()
|
||||
end,
|
||||
}),
|
||||
['<Tab>'] = mapping({
|
||||
c = function(fallback)
|
||||
c = function()
|
||||
local cmp = require('cmp')
|
||||
if #cmp.core:get_sources() > 0 and not cmp.get_config().experimental.native_menu then
|
||||
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
|
||||
fallback()
|
||||
if vim.fn.pumvisible() == 0 then
|
||||
vim.api.nvim_feedkeys(keymap.t('<C-z>'), 'in', true)
|
||||
else
|
||||
vim.api.nvim_feedkeys(keymap.t('<C-n>'), 'in', true)
|
||||
end
|
||||
end
|
||||
end,
|
||||
}),
|
||||
['<S-Tab>'] = mapping({
|
||||
c = function(fallback)
|
||||
c = function()
|
||||
local cmp = require('cmp')
|
||||
if #cmp.core:get_sources() > 0 and not cmp.get_config().experimental.native_menu then
|
||||
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
|
||||
fallback()
|
||||
if vim.fn.pumvisible() == 0 then
|
||||
vim.api.nvim_feedkeys(keymap.t('<C-z><C-p><C-p>'), 'in', true)
|
||||
else
|
||||
vim.api.nvim_feedkeys(keymap.t('<C-p>'), 'in', true)
|
||||
end
|
||||
end
|
||||
end,
|
||||
}),
|
||||
@ -113,6 +79,21 @@ return function()
|
||||
['<C-e>'] = mapping.abort(),
|
||||
},
|
||||
|
||||
snippet = {
|
||||
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',
|
||||
},
|
||||
|
||||
formatting = {
|
||||
fields = { 'abbr', 'kind', 'menu' },
|
||||
format = function(_, vim_item)
|
||||
@ -120,11 +101,52 @@ return function()
|
||||
end,
|
||||
},
|
||||
|
||||
experimental = {
|
||||
native_menu = false,
|
||||
ghost_text = false,
|
||||
matching = {
|
||||
disallow_fuzzy_matching = false,
|
||||
disallow_partial_matching = false,
|
||||
disallow_prefix_unmatching = false,
|
||||
},
|
||||
|
||||
sorting = {
|
||||
priority_weight = 2,
|
||||
comparators = {
|
||||
compare.offset,
|
||||
compare.exact,
|
||||
-- compare.scopes,
|
||||
compare.score,
|
||||
compare.recently_used,
|
||||
compare.locality,
|
||||
compare.kind,
|
||||
compare.sort_text,
|
||||
compare.length,
|
||||
compare.order,
|
||||
},
|
||||
},
|
||||
|
||||
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)
|
||||
return commit_characters
|
||||
end,
|
||||
},
|
||||
|
||||
event = {},
|
||||
|
||||
experimental = {
|
||||
ghost_text = false,
|
||||
},
|
||||
|
||||
view = {
|
||||
entries = { name = 'custom', selection_order = 'top_down' },
|
||||
},
|
||||
}
|
||||
end
|
||||
|
@ -13,9 +13,19 @@ mapping = setmetatable({}, {
|
||||
})
|
||||
|
||||
---Invoke completion
|
||||
mapping.complete = function()
|
||||
---@param option cmp.CompleteParams
|
||||
mapping.complete = function(option)
|
||||
return function(fallback)
|
||||
if not require('cmp').complete() then
|
||||
if not require('cmp').complete(option) then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Complete common string.
|
||||
mapping.complete_common_string = function()
|
||||
return function(fallback)
|
||||
if not require('cmp').complete_common_string() then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
|
@ -1,4 +1,5 @@
|
||||
local debug = require('cmp.utils.debug')
|
||||
local str = require('cmp.utils.str')
|
||||
local char = require('cmp.utils.char')
|
||||
local pattern = require('cmp.utils.pattern')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
@ -14,14 +15,13 @@ local api = require('cmp.utils.api')
|
||||
local event = require('cmp.utils.event')
|
||||
|
||||
local SOURCE_TIMEOUT = 500
|
||||
local THROTTLE_TIME = 120
|
||||
local DEBOUNCE_TIME = 20
|
||||
local DEBOUNCE_TIME = 80
|
||||
local THROTTLE_TIME = 40
|
||||
|
||||
---@class cmp.Core
|
||||
---@field public suspending boolean
|
||||
---@field public view cmp.View
|
||||
---@field public sources cmp.Source[]
|
||||
---@field public sources_by_name table<string, cmp.Source>
|
||||
---@field public context cmp.Context
|
||||
---@field public event cmp.Event
|
||||
local core = {}
|
||||
@ -30,13 +30,15 @@ core.new = function()
|
||||
local self = setmetatable({}, { __index = core })
|
||||
self.suspending = false
|
||||
self.sources = {}
|
||||
self.sources_by_name = {}
|
||||
self.context = context.new()
|
||||
self.event = event.new()
|
||||
self.view = view.new()
|
||||
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)
|
||||
return self
|
||||
end
|
||||
|
||||
@ -44,19 +46,11 @@ end
|
||||
---@param s cmp.Source
|
||||
core.register_source = function(self, s)
|
||||
self.sources[s.id] = s
|
||||
if not self.sources_by_name[s.name] then
|
||||
self.sources_by_name[s.name] = {}
|
||||
end
|
||||
table.insert(self.sources_by_name[s.name], s)
|
||||
end
|
||||
|
||||
---Unregister source
|
||||
---@param source_id string
|
||||
core.unregister_source = function(self, source_id)
|
||||
local name = self.sources[source_id].name
|
||||
self.sources_by_name[name] = vim.tbl_filter(function(s)
|
||||
return s.id ~= source_id
|
||||
end, self.sources_by_name[name])
|
||||
self.sources[source_id] = nil
|
||||
end
|
||||
|
||||
@ -86,14 +80,23 @@ core.suspend = function(self)
|
||||
end
|
||||
|
||||
---Get sources that sorted by priority
|
||||
---@param statuses cmp.SourceStatus[]
|
||||
---@param filter cmp.SourceStatus[]|fun(s: cmp.Source): boolean
|
||||
---@return cmp.Source[]
|
||||
core.get_sources = function(self, statuses)
|
||||
core.get_sources = function(self, filter)
|
||||
local f = function(s)
|
||||
if type(filter) == 'table' then
|
||||
return vim.tbl_contains(filter, s.status)
|
||||
elseif type(filter) == 'function' then
|
||||
return filter(s)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local sources = {}
|
||||
for _, c in pairs(config.get().sources) do
|
||||
for _, s in ipairs(self.sources_by_name[c.name] or {}) do
|
||||
if not statuses or vim.tbl_contains(statuses, s.status) then
|
||||
if s:is_available() then
|
||||
for _, s in pairs(self.sources) do
|
||||
if c.name == s.name then
|
||||
if s:is_available() and f(s) then
|
||||
table.insert(sources, s)
|
||||
end
|
||||
end
|
||||
@ -118,6 +121,7 @@ core.on_keymap = function(self, keys, fallback)
|
||||
local is_printable = char.is_printable(string.byte(chars, 1))
|
||||
self:confirm(e, {
|
||||
behavior = is_printable and 'insert' or 'replace',
|
||||
commit_character = chars,
|
||||
}, function()
|
||||
local ctx = self:get_context()
|
||||
local word = e:get_word()
|
||||
@ -154,7 +158,6 @@ core.on_change = function(self, trigger_event)
|
||||
self:get_context({ reason = types.cmp.ContextReason.Auto })
|
||||
return
|
||||
end
|
||||
|
||||
self:autoindent(trigger_event, function()
|
||||
local ctx = self:get_context({ reason = types.cmp.ContextReason.Auto })
|
||||
debug.log(('ctx: `%s`'):format(ctx.cursor_before_line))
|
||||
@ -165,7 +168,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 = THROTTLE_TIME
|
||||
self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
|
||||
self:filter()
|
||||
end
|
||||
else
|
||||
@ -204,92 +207,140 @@ core.autoindent = function(self, trigger_event, callback)
|
||||
return callback()
|
||||
end
|
||||
|
||||
-- Scan indentkeys.
|
||||
-- Reset current completion if indentkeys matched.
|
||||
for _, key in ipairs(vim.split(vim.bo.indentkeys, ',')) do
|
||||
if vim.tbl_contains({ '=' .. prefix, '0=' .. prefix }, key) then
|
||||
local release = self:suspend()
|
||||
vim.schedule(function() -- Check autoindent already applied.
|
||||
if cursor_before_line == api.get_cursor_before_line() then
|
||||
feedkeys.call(keymap.autoindent(), 'n', function()
|
||||
release()
|
||||
callback()
|
||||
end)
|
||||
else
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
return
|
||||
self:reset()
|
||||
self:set_context(context.empty())
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- indentkeys does not matched.
|
||||
callback()
|
||||
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
|
||||
return false
|
||||
end
|
||||
|
||||
config.set_onetime({
|
||||
sources = config.get().sources,
|
||||
matching = {
|
||||
disallow_prefix_unmatching = true,
|
||||
disallow_partial_matching = true,
|
||||
disallow_fuzzy_matching = true,
|
||||
},
|
||||
})
|
||||
|
||||
self:filter()
|
||||
self.filter:sync(1000)
|
||||
|
||||
config.set_onetime({})
|
||||
|
||||
local cursor = api.get_cursor()
|
||||
local offset = self.view:get_offset()
|
||||
local common_string
|
||||
for _, e in ipairs(self.view:get_entries()) do
|
||||
local vim_item = e:get_vim_item(offset)
|
||||
if not common_string then
|
||||
common_string = vim_item.word
|
||||
else
|
||||
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')
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Invoke completion
|
||||
---@param ctx cmp.Context
|
||||
core.complete = function(self, ctx)
|
||||
if not api.is_suitable_mode() then
|
||||
return
|
||||
end
|
||||
|
||||
self:set_context(ctx)
|
||||
|
||||
for _, s in ipairs(self:get_sources({ source.SourceStatus.WAITING, source.SourceStatus.COMPLETED })) do
|
||||
s:complete(
|
||||
ctx,
|
||||
(function(src)
|
||||
local callback
|
||||
callback = function()
|
||||
local new = context.new(ctx)
|
||||
if new:changed(new.prev_context) and ctx == self.context then
|
||||
src:complete(new, callback)
|
||||
else
|
||||
-- Invoke completion sources.
|
||||
local sources = self:get_sources()
|
||||
for _, s in ipairs(sources) do
|
||||
local callback
|
||||
callback = (function(s_)
|
||||
return function()
|
||||
local new = context.new(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 = DEBOUNCE_TIME
|
||||
self.filter.timeout = self.view:visible() and DEBOUNCE_TIME or 0
|
||||
self:filter()
|
||||
end
|
||||
end
|
||||
return callback
|
||||
end)(s)
|
||||
)
|
||||
end
|
||||
end)(s)
|
||||
s:complete(ctx, callback)
|
||||
end
|
||||
|
||||
self.filter.timeout = THROTTLE_TIME
|
||||
self:filter()
|
||||
if not self.view:get_active_entry() then
|
||||
self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
|
||||
self:filter()
|
||||
end
|
||||
end
|
||||
|
||||
---Update completion menu
|
||||
core.filter = async.throttle(
|
||||
vim.schedule_wrap(function(self)
|
||||
if not api.is_suitable_mode() then
|
||||
return
|
||||
end
|
||||
if self.view:get_active_entry() ~= nil then
|
||||
return
|
||||
end
|
||||
local ctx = self:get_context()
|
||||
core.filter = async.throttle(function(self)
|
||||
self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
|
||||
|
||||
-- To wait for processing source for that's timeout.
|
||||
local sources = {}
|
||||
for _, s in ipairs(self:get_sources({ source.SourceStatus.FETCHING, source.SourceStatus.COMPLETED })) do
|
||||
local time = SOURCE_TIMEOUT - s:get_fetching_time()
|
||||
if not s.incomplete and time > 0 then
|
||||
if #sources == 0 then
|
||||
self.filter.stop()
|
||||
self.filter.timeout = time + 1
|
||||
self:filter()
|
||||
return
|
||||
end
|
||||
break
|
||||
end
|
||||
table.insert(sources, s)
|
||||
end
|
||||
self.filter.timeout = THROTTLE_TIME
|
||||
-- Check invalid condition.
|
||||
local ignore = false
|
||||
ignore = ignore or not api.is_suitable_mode()
|
||||
if ignore then
|
||||
return
|
||||
end
|
||||
|
||||
self.view:open(ctx, sources)
|
||||
end),
|
||||
THROTTLE_TIME
|
||||
)
|
||||
-- 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()
|
||||
self:filter()
|
||||
break
|
||||
end
|
||||
table.insert(sources, s)
|
||||
end
|
||||
|
||||
local ctx = self:get_context()
|
||||
|
||||
-- Display completion results.
|
||||
self.view:open(ctx, sources)
|
||||
|
||||
-- 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
|
||||
config.set_onetime({})
|
||||
end
|
||||
end, THROTTLE_TIME)
|
||||
|
||||
---Confirm completion.
|
||||
---@param e cmp.Entry
|
||||
@ -308,10 +359,11 @@ core.confirm = function(self, e, option, callback)
|
||||
-- Close menus.
|
||||
self.view:close()
|
||||
|
||||
feedkeys.call(keymap.indentkeys(), 'n')
|
||||
feedkeys.call('', 'n', function()
|
||||
local ctx = context.new()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor.character - vim.str_utfindex(ctx.cursor_line, e:get_offset() - 1)))
|
||||
table.insert(keys, keymap.backspace(ctx.cursor.character - misc.to_utfindex(ctx.cursor_line, e:get_offset())))
|
||||
table.insert(keys, e:get_word())
|
||||
table.insert(keys, keymap.undobreak())
|
||||
feedkeys.call(table.concat(keys, ''), 'int')
|
||||
@ -320,9 +372,9 @@ core.confirm = function(self, e, option, callback)
|
||||
local ctx = context.new()
|
||||
if api.is_cmdline_mode() then
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor.character - vim.str_utfindex(ctx.cursor_line, e:get_offset() - 1)))
|
||||
table.insert(keys, keymap.backspace(ctx.cursor.character - misc.to_utfindex(ctx.cursor_line, e:get_offset())))
|
||||
table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset()))
|
||||
feedkeys.call(table.concat(keys, ''), 'int')
|
||||
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()),
|
||||
@ -331,8 +383,8 @@ core.confirm = function(self, e, option, callback)
|
||||
end
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
local ctx = context.new()
|
||||
if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then
|
||||
local pre = context.new()
|
||||
e:resolve(function()
|
||||
local new = context.new()
|
||||
local text_edits = misc.safe(e:get_completion_item().additionalTextEdits) or {}
|
||||
@ -341,8 +393,8 @@ core.confirm = function(self, e, option, callback)
|
||||
end
|
||||
|
||||
local has_cursor_line_text_edit = (function()
|
||||
local minrow = math.min(pre.cursor.row, new.cursor.row)
|
||||
local maxrow = math.max(pre.cursor.row, new.cursor.row)
|
||||
local minrow = math.min(ctx.cursor.row, new.cursor.row)
|
||||
local maxrow = math.max(ctx.cursor.row, new.cursor.row)
|
||||
for _, te in ipairs(text_edits) do
|
||||
local srow = te.range.start.line + 1
|
||||
local erow = te.range['end'].line + 1
|
||||
@ -355,10 +407,10 @@ core.confirm = function(self, e, option, callback)
|
||||
if has_cursor_line_text_edit then
|
||||
return
|
||||
end
|
||||
vim.fn['cmp#apply_text_edits'](new.bufnr, text_edits)
|
||||
vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, 'utf-16')
|
||||
end)
|
||||
else
|
||||
vim.fn['cmp#apply_text_edits'](vim.api.nvim_get_current_buf(), e:get_completion_item().additionalTextEdits)
|
||||
vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, 'utf-16')
|
||||
end
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
@ -375,8 +427,8 @@ core.confirm = function(self, e, option, callback)
|
||||
completion_item.textEdit.range = e:get_insert_range()
|
||||
end
|
||||
|
||||
local diff_before = e.context.cursor.character - completion_item.textEdit.range.start.character
|
||||
local diff_after = completion_item.textEdit.range['end'].character - e.context.cursor.character
|
||||
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 new_text = completion_item.textEdit.newText
|
||||
|
||||
if api.is_insert_mode() then
|
||||
@ -388,14 +440,14 @@ core.confirm = function(self, e, option, callback)
|
||||
if is_snippet then
|
||||
completion_item.textEdit.newText = ''
|
||||
end
|
||||
vim.fn['cmp#apply_text_edits'](ctx.bufnr, { completion_item.textEdit })
|
||||
vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-16')
|
||||
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 + vim.str_utfindex(texts[1])
|
||||
position.character = position.character + misc.to_utfindex(texts[1])
|
||||
else
|
||||
position.character = vim.str_utfindex(texts[#texts])
|
||||
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 })
|
||||
@ -410,13 +462,17 @@ core.confirm = function(self, e, option, callback)
|
||||
table.insert(keys, string.rep(keymap.t('<BS>'), diff_before))
|
||||
table.insert(keys, string.rep(keymap.t('<Del>'), diff_after))
|
||||
table.insert(keys, new_text)
|
||||
feedkeys.call(table.concat(keys, ''), 'int')
|
||||
feedkeys.call(table.concat(keys, ''), 'in')
|
||||
end
|
||||
end)
|
||||
feedkeys.call(keymap.indentkeys(vim.bo.indentkeys), 'n')
|
||||
feedkeys.call('', 'n', function()
|
||||
e:execute(vim.schedule_wrap(function()
|
||||
release()
|
||||
self.event:emit('confirm_done', e)
|
||||
self.event:emit('confirm_done', {
|
||||
entry = e,
|
||||
commit_character = option.commit_character,
|
||||
})
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
@ -429,7 +485,7 @@ core.reset = function(self)
|
||||
for _, s in pairs(self.sources) do
|
||||
s:reset()
|
||||
end
|
||||
self:get_context() -- To prevent new event
|
||||
self.context = context.empty()
|
||||
end
|
||||
|
||||
return core
|
||||
|
@ -54,10 +54,10 @@ end
|
||||
---Make offset value
|
||||
---@return number
|
||||
entry.get_offset = function(self)
|
||||
return self.cache:ensure('get_offset', function()
|
||||
return self.cache:ensure({ 'get_offset', self.resolved_completion_item and 1 or 0 }, function()
|
||||
local offset = self.source_offset
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
local range = misc.safe(self.completion_item.textEdit.insert) or misc.safe(self.completion_item.textEdit.range)
|
||||
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 range then
|
||||
local c = misc.to_vimindex(self.context.cursor_line, range.start.character)
|
||||
for idx = c, self.source_offset do
|
||||
@ -98,28 +98,32 @@ entry.get_offset = function(self)
|
||||
end
|
||||
|
||||
---Create word for vim.CompletedItem
|
||||
---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.completion_item.word) then
|
||||
return self.completion_item.word
|
||||
if misc.safe(self:get_completion_item().word) then
|
||||
return self:get_completion_item().word
|
||||
end
|
||||
|
||||
local word
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
word = str.trim(self.completion_item.textEdit.newText)
|
||||
local overwrite = self:get_overwrite()
|
||||
if 0 < overwrite[2] or self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
word = str.get_word(word, string.byte(self.context.cursor_after_line, 1))
|
||||
if misc.safe(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)
|
||||
end
|
||||
elseif misc.safe(self.completion_item.insertText) then
|
||||
word = str.trim(self.completion_item.insertText)
|
||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
word = str.get_word(word)
|
||||
local overwrite = self:get_overwrite()
|
||||
if 0 < overwrite[2] or self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
word = str.get_word(word, string.byte(self.context.cursor_after_line, 1), overwrite[1] or 0)
|
||||
end
|
||||
elseif not misc.empty(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.get_word(vim.lsp.util.parse_snippet(word))
|
||||
end
|
||||
else
|
||||
word = str.trim(self.completion_item.label)
|
||||
word = str.trim(self:get_completion_item().label)
|
||||
end
|
||||
return str.oneline(word)
|
||||
end)
|
||||
@ -128,9 +132,9 @@ end
|
||||
---Get overwrite information
|
||||
---@return number, number
|
||||
entry.get_overwrite = function(self)
|
||||
return self.cache:ensure('get_overwrite', function()
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
local r = misc.safe(self.completion_item.textEdit.insert) or misc.safe(self.completion_item.textEdit.range)
|
||||
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
|
||||
@ -144,27 +148,13 @@ end
|
||||
---Create filter text
|
||||
---@return string
|
||||
entry.get_filter_text = function(self)
|
||||
return self.cache:ensure('get_filter_text', function()
|
||||
return self.cache:ensure({ 'get_filter_text', self.resolved_completion_item and 1 or 0 }, function()
|
||||
local word
|
||||
if misc.safe(self.completion_item.filterText) then
|
||||
word = self.completion_item.filterText
|
||||
if misc.safe(self:get_completion_item().filterText) then
|
||||
word = self:get_completion_item().filterText
|
||||
else
|
||||
word = str.trim(self.completion_item.label)
|
||||
word = str.trim(self:get_completion_item().label)
|
||||
end
|
||||
|
||||
-- @see https://github.com/clangd/clangd/issues/815
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
local diff = self.source_offset - self:get_offset()
|
||||
if diff > 0 then
|
||||
if char.is_symbol(string.byte(self.context.cursor_line, self:get_offset())) then
|
||||
local prefix = string.sub(self.context.cursor_line, self:get_offset(), self:get_offset() + diff)
|
||||
if string.find(word, prefix, 1, true) ~= 1 then
|
||||
word = prefix .. word
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return word
|
||||
end)
|
||||
end
|
||||
@ -172,20 +162,20 @@ end
|
||||
---Get LSP's insert text
|
||||
---@return string
|
||||
entry.get_insert_text = function(self)
|
||||
return self.cache:ensure('get_insert_text', function()
|
||||
return self.cache:ensure({ 'get_insert_text', self.resolved_completion_item and 1 or 0 }, function()
|
||||
local word
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
word = str.trim(self.completion_item.textEdit.newText)
|
||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
if misc.safe(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.completion_item.insertText) then
|
||||
word = str.trim(self.completion_item.insertText)
|
||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
elseif misc.safe(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}')
|
||||
end
|
||||
else
|
||||
word = str.trim(self.completion_item.label)
|
||||
word = str.trim(self:get_completion_item().label)
|
||||
end
|
||||
return word
|
||||
end)
|
||||
@ -194,31 +184,38 @@ end
|
||||
---Return the item is deprecated or not.
|
||||
---@return boolean
|
||||
entry.is_deprecated = function(self)
|
||||
return self.completion_item.deprecated or vim.tbl_contains(self.completion_item.tags or {}, types.lsp.CompletionItemTag.Deprecated)
|
||||
return self:get_completion_item().deprecated or vim.tbl_contains(self:get_completion_item().tags or {}, types.lsp.CompletionItemTag.Deprecated)
|
||||
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 } }
|
||||
entry.get_view = function(self, suggest_offset)
|
||||
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 }, function()
|
||||
return self.cache:ensure({ 'get_view', self.resolved_completion_item and 1 or 0, entries_buf }, function()
|
||||
local view = {}
|
||||
view.abbr = {}
|
||||
view.abbr.text = item.abbr or ''
|
||||
view.abbr.bytes = #view.abbr.text
|
||||
view.abbr.width = vim.str_utfindex(view.abbr.text)
|
||||
view.abbr.hl_group = self:is_deprecated() and 'CmpItemAbbrDeprecated' or 'CmpItemAbbr'
|
||||
view.kind = {}
|
||||
view.kind.text = item.kind or ''
|
||||
view.kind.bytes = #view.kind.text
|
||||
view.kind.width = vim.str_utfindex(view.kind.text)
|
||||
view.kind.hl_group = 'CmpItemKind'
|
||||
view.menu = {}
|
||||
view.menu.text = item.menu or ''
|
||||
view.menu.bytes = #view.menu.text
|
||||
view.menu.width = vim.str_utfindex(view.menu.text)
|
||||
view.menu.hl_group = 'CmpItemMenu'
|
||||
view.dup = item.dup
|
||||
-- The result of vim.fn.strdisplaywidth depends on which buffer it was
|
||||
-- called in because it reads the values of the option 'tabstop' when
|
||||
-- rendering <Tab> characters.
|
||||
vim.api.nvim_buf_call(entries_buf, function()
|
||||
view.abbr = {}
|
||||
view.abbr.text = item.abbr or ''
|
||||
view.abbr.bytes = #view.abbr.text
|
||||
view.abbr.width = vim.fn.strdisplaywidth(view.abbr.text)
|
||||
view.abbr.hl_group = item.abbr_hl_group or (self:is_deprecated() and 'CmpItemAbbrDeprecated' or 'CmpItemAbbr')
|
||||
view.kind = {}
|
||||
view.kind.text = item.kind or ''
|
||||
view.kind.bytes = #view.kind.text
|
||||
view.kind.width = vim.fn.strdisplaywidth(view.kind.text)
|
||||
view.kind.hl_group = item.kind_hl_group or ('CmpItemKind' .. (types.lsp.CompletionItemKind[self:get_kind()] or ''))
|
||||
view.menu = {}
|
||||
view.menu.text = item.menu or ''
|
||||
view.menu.bytes = #view.menu.text
|
||||
view.menu.width = vim.fn.strdisplaywidth(view.menu.text)
|
||||
view.menu.hl_group = item.menu_hl_group or 'CmpItemMenu'
|
||||
view.dup = item.dup
|
||||
end)
|
||||
return view
|
||||
end)
|
||||
end
|
||||
@ -233,13 +230,16 @@ entry.get_vim_item = function(self, suggest_offset)
|
||||
local abbr = str.oneline(completion_item.label)
|
||||
|
||||
-- ~ indicator
|
||||
local is_snippet = false
|
||||
if #(misc.safe(completion_item.additionalTextEdits) or {}) > 0 then
|
||||
abbr = abbr .. '~'
|
||||
is_snippet = true
|
||||
elseif completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
local insert_text = self:get_insert_text()
|
||||
if word ~= insert_text then
|
||||
abbr = abbr .. '~'
|
||||
end
|
||||
is_snippet = self:get_insert_text() ~= word
|
||||
elseif completion_item.kind == types.lsp.CompletionItemKind.Snippet then
|
||||
is_snippet = true
|
||||
end
|
||||
if is_snippet then
|
||||
abbr = abbr .. '~'
|
||||
end
|
||||
|
||||
-- append delta text
|
||||
@ -260,10 +260,12 @@ entry.get_vim_item = function(self, suggest_offset)
|
||||
end
|
||||
|
||||
-- remove duplicated string.
|
||||
for i = 1, #word - 1 do
|
||||
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
|
||||
word = string.sub(word, 1, i - 1)
|
||||
break
|
||||
if self:get_offset() ~= self.context.cursor.col then
|
||||
for i = 1, #word - 1 do
|
||||
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
|
||||
word = string.sub(word, 1, i - 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -272,7 +274,7 @@ entry.get_vim_item = function(self, suggest_offset)
|
||||
abbr = abbr,
|
||||
kind = types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
|
||||
menu = menu,
|
||||
dup = self.completion_item.dup or 1,
|
||||
dup = self:get_completion_item().dup or 1,
|
||||
}
|
||||
if config.get().formatting.format then
|
||||
vim_item = config.get().formatting.format(self, vim_item)
|
||||
@ -298,11 +300,11 @@ end
|
||||
---@return lsp.Range|nil
|
||||
entry.get_insert_range = function(self)
|
||||
local insert_range
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
if misc.safe(self.completion_item.textEdit.insert) then
|
||||
insert_range = self.completion_item.textEdit.insert
|
||||
if misc.safe(self:get_completion_item().textEdit) then
|
||||
if misc.safe(self:get_completion_item().textEdit.insert) then
|
||||
insert_range = self:get_completion_item().textEdit.insert
|
||||
else
|
||||
insert_range = self.completion_item.textEdit.range
|
||||
insert_range = self:get_completion_item().textEdit.range
|
||||
end
|
||||
else
|
||||
insert_range = {
|
||||
@ -319,14 +321,10 @@ end
|
||||
---Return replace range
|
||||
---@return lsp.Range|nil
|
||||
entry.get_replace_range = function(self)
|
||||
return self.cache:ensure('get_replace_range', function()
|
||||
return self.cache:ensure({ 'get_replace_range', self.resolved_completion_item and 1 or 0 }, function()
|
||||
local replace_range
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
if misc.safe(self.completion_item.textEdit.replace) then
|
||||
replace_range = self.completion_item.textEdit.replace
|
||||
else
|
||||
replace_range = self.completion_item.textEdit.range
|
||||
end
|
||||
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
|
||||
replace_range = {
|
||||
start = {
|
||||
@ -342,14 +340,49 @@ end
|
||||
|
||||
---Match line.
|
||||
---@param input string
|
||||
---@param matching_config cmp.MatchingConfig
|
||||
---@return { score: number, matches: table[] }
|
||||
entry.match = function(self, input)
|
||||
return self.match_cache:ensure(input, function()
|
||||
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()
|
||||
local option = {
|
||||
disallow_fuzzy_matching = matching_config.disallow_fuzzy_matching,
|
||||
disallow_partial_matching = matching_config.disallow_partial_matching,
|
||||
disallow_prefix_unmatching = matching_config.disallow_prefix_unmatching,
|
||||
synonyms = {
|
||||
self:get_word(),
|
||||
self:get_completion_item().label,
|
||||
},
|
||||
}
|
||||
|
||||
local score, matches, _
|
||||
score, matches = matcher.match(input, self:get_filter_text(), { self:get_word(), self:get_completion_item().label })
|
||||
score, matches = matcher.match(input, self:get_filter_text(), option)
|
||||
|
||||
-- Support the language server that doesn't respect VSCode's behaviors.
|
||||
if score == 0 then
|
||||
if misc.safe(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
|
||||
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)
|
||||
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() })
|
||||
end
|
||||
|
||||
return { score = score, matches = matches }
|
||||
end)
|
||||
end
|
||||
@ -357,12 +390,12 @@ 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', self.resolved_completion_item and 1 or 0 }, function()
|
||||
if self.resolved_completion_item then
|
||||
local completion_item = misc.copy(self.completion_item)
|
||||
completion_item.detail = self.resolved_completion_item.detail or completion_item.detail
|
||||
completion_item.documentation = self.resolved_completion_item.documentation or completion_item.documentation
|
||||
completion_item.additionalTextEdits = self.resolved_completion_item.additionalTextEdits or completion_item.additionalTextEdits
|
||||
for k, v in pairs(self.resolved_completion_item) do
|
||||
completion_item[k] = v or completion_item[k]
|
||||
end
|
||||
return completion_item
|
||||
end
|
||||
return self.completion_item
|
||||
@ -378,9 +411,14 @@ entry.get_documentation = function(self)
|
||||
|
||||
-- detail
|
||||
if misc.safe(item.detail) and item.detail ~= '' then
|
||||
local ft = self.context.filetype
|
||||
local dot_index = string.find(ft, '%.')
|
||||
if dot_index ~= nil then
|
||||
ft = string.sub(ft, 0, dot_index - 1)
|
||||
end
|
||||
table.insert(documents, {
|
||||
kind = types.lsp.MarkupKind.Markdown,
|
||||
value = ('```%s\n%s\n```'):format(self.context.filetype, str.trim(item.detail)),
|
||||
value = ('```%s\n%s\n```'):format(ft, str.trim(item.detail)),
|
||||
})
|
||||
end
|
||||
|
||||
@ -399,7 +437,7 @@ end
|
||||
---Get completion item kind
|
||||
---@return lsp.CompletionItemKind
|
||||
entry.get_kind = function(self)
|
||||
return misc.safe(self.completion_item.kind) or types.lsp.CompletionItemKind.Text
|
||||
return misc.safe(self:get_completion_item().kind) or types.lsp.CompletionItemKind.Text
|
||||
end
|
||||
|
||||
---Execute completion item's command.
|
||||
|
@ -1,4 +1,6 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
local source = require('cmp.source')
|
||||
local async = require('cmp.utils.async')
|
||||
|
||||
local entry = require('cmp.entry')
|
||||
|
||||
@ -101,7 +103,7 @@ describe('entry', function()
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(4).word, '->foo')
|
||||
assert.are.equal(e:get_filter_text(), '.foo')
|
||||
assert.are.equal(e:get_filter_text(), 'foo')
|
||||
end)
|
||||
|
||||
it('[typescript-language-server] 1', function()
|
||||
@ -264,6 +266,65 @@ describe('entry', function()
|
||||
assert.are.equal(e:get_filter_text(), '$this')
|
||||
end)
|
||||
|
||||
it('[odin-language-server] 1', function()
|
||||
local state = spec.state('\t\t', 1, 4)
|
||||
|
||||
-- press g
|
||||
state.input('s')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
additionalTextEdits = {},
|
||||
command = {
|
||||
arguments = {},
|
||||
command = '',
|
||||
title = '',
|
||||
},
|
||||
deprecated = false,
|
||||
detail = 'string',
|
||||
documentation = '',
|
||||
insertText = '',
|
||||
insertTextFormat = 1,
|
||||
kind = 14,
|
||||
label = 'string',
|
||||
tags = {},
|
||||
})
|
||||
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)
|
||||
|
||||
|
@ -17,17 +17,33 @@ end
|
||||
cmp.lsp = require('cmp.types.lsp')
|
||||
cmp.vim = require('cmp.types.vim')
|
||||
|
||||
---Export default config presets.
|
||||
---Expose event
|
||||
cmp.event = cmp.core.event
|
||||
|
||||
---Export mapping for special case
|
||||
cmp.mapping = require('cmp.config.mapping')
|
||||
|
||||
---Export default config presets
|
||||
cmp.config = {}
|
||||
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')
|
||||
|
||||
---Expose event
|
||||
cmp.event = cmp.core.event
|
||||
---Sync asynchronous process.
|
||||
cmp.sync = function(callback)
|
||||
return function(...)
|
||||
cmp.core.filter:sync(1000)
|
||||
if callback then
|
||||
return callback(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Export mapping
|
||||
cmp.mapping = require('cmp.config.mapping')
|
||||
---Suspend completion.
|
||||
cmp.suspend = function()
|
||||
return cmp.core:suspend()
|
||||
end
|
||||
|
||||
---Register completion sources
|
||||
---@param name string
|
||||
@ -52,28 +68,41 @@ cmp.get_config = function()
|
||||
end
|
||||
|
||||
---Invoke completion manually
|
||||
cmp.complete = function()
|
||||
cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.Manual }))
|
||||
---@param option cmp.CompleteParams
|
||||
cmp.complete = cmp.sync(function(option)
|
||||
option = option or {}
|
||||
config.set_onetime(option.config)
|
||||
cmp.core:complete(cmp.core:get_context({ reason = option.reason or cmp.ContextReason.Manual }))
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
---Complete common string in current entries.
|
||||
cmp.complete_common_string = cmp.sync(function()
|
||||
return cmp.core:complete_common_string()
|
||||
end)
|
||||
|
||||
---Return view is visible or not.
|
||||
cmp.visible = function()
|
||||
cmp.visible = cmp.sync(function()
|
||||
return cmp.core.view:visible() or vim.fn.pumvisible() == 1
|
||||
end
|
||||
end)
|
||||
|
||||
---Get current selected entry or nil
|
||||
cmp.get_selected_entry = function()
|
||||
cmp.get_selected_entry = cmp.sync(function()
|
||||
return cmp.core.view:get_selected_entry()
|
||||
end
|
||||
end)
|
||||
|
||||
---Get current active entry or nil
|
||||
cmp.get_active_entry = function()
|
||||
cmp.get_active_entry = cmp.sync(function()
|
||||
return cmp.core.view:get_active_entry()
|
||||
end
|
||||
end)
|
||||
|
||||
---Get current all entries
|
||||
cmp.get_entries = cmp.sync(function()
|
||||
return cmp.core.view:get_entries()
|
||||
end)
|
||||
|
||||
---Close current completion
|
||||
cmp.close = function()
|
||||
cmp.close = cmp.sync(function()
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:close()
|
||||
@ -83,10 +112,10 @@ cmp.close = function()
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
---Abort current completion
|
||||
cmp.abort = function()
|
||||
cmp.abort = cmp.sync(function()
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:abort()
|
||||
@ -95,83 +124,65 @@ cmp.abort = function()
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---Suspend completion.
|
||||
cmp.suspend = function()
|
||||
return cmp.core:suspend()
|
||||
end
|
||||
end)
|
||||
|
||||
---Select next item if possible
|
||||
cmp.select_next_item = function(option)
|
||||
cmp.select_next_item = cmp.sync(function(option)
|
||||
option = option or {}
|
||||
|
||||
-- Hack: Ignore when executing macro.
|
||||
if vim.fn.reg_executing() ~= '' then
|
||||
return true
|
||||
end
|
||||
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:select_next_item(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('<C-n>'), 'n')
|
||||
feedkeys.call(keymap.t('<C-n>'), 'in')
|
||||
else
|
||||
feedkeys.call(keymap.t('<Down>'), 'n')
|
||||
feedkeys.call(keymap.t('<Down>'), 'in')
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
---Select prev item if possible
|
||||
cmp.select_prev_item = function(option)
|
||||
cmp.select_prev_item = cmp.sync(function(option)
|
||||
option = option or {}
|
||||
|
||||
-- Hack: Ignore when executing macro.
|
||||
if vim.fn.reg_executing() ~= '' then
|
||||
return true
|
||||
end
|
||||
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:select_prev_item(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('<C-p>'), 'n')
|
||||
feedkeys.call(keymap.t('<C-p>'), 'in')
|
||||
else
|
||||
feedkeys.call(keymap.t('<Up>'), 'n')
|
||||
feedkeys.call(keymap.t('<Up>'), 'in')
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
---Scrolling documentation window if possible
|
||||
cmp.scroll_docs = function(delta)
|
||||
cmp.scroll_docs = cmp.sync(function(delta)
|
||||
if cmp.core.view:visible() then
|
||||
cmp.core.view:scroll_docs(delta)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
---Confirm completion
|
||||
cmp.confirm = function(option, callback)
|
||||
cmp.confirm = cmp.sync(function(option, callback)
|
||||
option = option or {}
|
||||
callback = callback or function() end
|
||||
|
||||
-- Hack: Ignore when executing macro.
|
||||
if vim.fn.reg_executing() ~= '' then
|
||||
return true
|
||||
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, {
|
||||
@ -182,13 +193,14 @@ cmp.confirm = function(option, callback)
|
||||
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('<C-y>'), 'n')
|
||||
feedkeys.call(keymap.t('<C-y>'), 'in')
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
---Show status
|
||||
cmp.status = function()
|
||||
@ -255,6 +267,9 @@ cmp.setup = setmetatable({
|
||||
global = function(c)
|
||||
config.set_global(c)
|
||||
end,
|
||||
filetype = function(filetype, c)
|
||||
config.set_filetype(c, filetype)
|
||||
end,
|
||||
buffer = function(c)
|
||||
config.set_buffer(c, vim.api.nvim_get_current_buf())
|
||||
end,
|
||||
@ -302,11 +317,29 @@ end)
|
||||
autocmd.subscribe('CursorMoved', function()
|
||||
if config.enabled() then
|
||||
cmp.core:on_moved()
|
||||
else
|
||||
cmp.core:reset()
|
||||
cmp.core.view:close()
|
||||
end
|
||||
end)
|
||||
|
||||
cmp.event:on('confirm_done', function(e)
|
||||
cmp.config.compare.recently_used:add_entry(e)
|
||||
autocmd.subscribe('InsertEnter', function()
|
||||
cmp.config.compare.scopes:update()
|
||||
cmp.config.compare.locality:update()
|
||||
end)
|
||||
|
||||
cmp.event:on('complete_done', function(evt)
|
||||
if evt.entry then
|
||||
cmp.config.compare.recently_used:add_entry(evt.entry)
|
||||
end
|
||||
cmp.config.compare.scopes:update()
|
||||
cmp.config.compare.locality:update()
|
||||
end)
|
||||
|
||||
cmp.event:on('confirm_done', function(evt)
|
||||
if evt.entry then
|
||||
cmp.config.compare.recently_used:add_entry(evt.entry)
|
||||
end
|
||||
end)
|
||||
|
||||
return cmp
|
||||
|
@ -72,9 +72,11 @@ end
|
||||
---Match entry
|
||||
---@param input string
|
||||
---@param word string
|
||||
---@param words string[]
|
||||
---@param option { synonyms: string[], disallow_fuzzy_matching: boolean, disallow_partial_matching: boolean, disallow_prefix_unmatching: boolean }
|
||||
---@return number
|
||||
matcher.match = function(input, word, words)
|
||||
matcher.match = function(input, word, option)
|
||||
option = option or {}
|
||||
|
||||
-- Empty input
|
||||
if #input == 0 then
|
||||
return matcher.PREFIX_FACTOR + matcher.NOT_FUZZY_FACTOR, {}
|
||||
@ -85,7 +87,14 @@ matcher.match = function(input, word, words)
|
||||
return 0, {}
|
||||
end
|
||||
|
||||
--- Gather matched regions
|
||||
-- Check prefix matching.
|
||||
if option.disallow_prefix_unmatching then
|
||||
if not char.match(string.byte(input, 1), string.byte(word, 1)) then
|
||||
return 0, {}
|
||||
end
|
||||
end
|
||||
|
||||
-- Gather matched regions
|
||||
local matches = {}
|
||||
local input_start_index = 1
|
||||
local input_end_index = 1
|
||||
@ -105,6 +114,11 @@ matcher.match = function(input, word, words)
|
||||
word_bound_index = word_bound_index + 1
|
||||
end
|
||||
|
||||
-- Check partial matching.
|
||||
if option.disallow_partial_matching and #matches > 1 then
|
||||
return 0, {}
|
||||
end
|
||||
|
||||
if #matches == 0 then
|
||||
return 0, {}
|
||||
end
|
||||
@ -116,11 +130,11 @@ matcher.match = function(input, word, words)
|
||||
if matches[1].input_match_start == 1 and matches[1].word_match_start == 1 then
|
||||
prefix = true
|
||||
else
|
||||
for _, w in ipairs(words or {}) do
|
||||
for _, synonym in ipairs(option.synonyms or {}) do
|
||||
prefix = true
|
||||
local o = 1
|
||||
for i = matches[1].input_match_start, matches[1].input_match_end do
|
||||
if not char.match(string.byte(w, o), string.byte(input, i)) then
|
||||
if not char.match(string.byte(synonym, o), string.byte(input, i)) then
|
||||
prefix = false
|
||||
break
|
||||
end
|
||||
@ -152,8 +166,10 @@ matcher.match = function(input, word, words)
|
||||
|
||||
-- Check remaining input as fuzzy
|
||||
if matches[#matches].input_match_end < #input then
|
||||
if prefix and matcher.fuzzy(input, word, matches) then
|
||||
return score, matches
|
||||
if not option.disallow_fuzzy_matching then
|
||||
if prefix and matcher.fuzzy(input, word, matches) then
|
||||
return score, matches
|
||||
end
|
||||
end
|
||||
return 0, {}
|
||||
end
|
||||
|
@ -16,20 +16,37 @@ describe('matcher', function()
|
||||
assert.is.truthy(matcher.match('woroff', 'word_offset') >= 1)
|
||||
assert.is.truthy(matcher.match('call', 'call') > matcher.match('call', 'condition_all'))
|
||||
assert.is.truthy(matcher.match('Buffer', 'Buffer') > matcher.match('Buffer', 'buffer'))
|
||||
assert.is.truthy(matcher.match('luacon', 'lua_context') > matcher.match('luacon', 'LuaContext'))
|
||||
assert.is.truthy(matcher.match('fmodify', 'fnamemodify') >= 1)
|
||||
assert.is.truthy(matcher.match('candlesingle', 'candle#accept#single') >= 1)
|
||||
assert.is.truthy(matcher.match('conso', 'console') > matcher.match('conso', 'ConstantSourceNode'))
|
||||
assert.is.truthy(matcher.match('var_', 'var_dump') >= 1)
|
||||
assert.is.truthy(matcher.match('my_', 'my_awesome_variable') > matcher.match('my_', 'completion_matching_strategy_list'))
|
||||
assert.is.truthy(matcher.match('luacon', 'lua_context') > matcher.match('luacon', 'LuaContext'))
|
||||
assert.is.truthy(matcher.match('call', 'calc') == 0)
|
||||
|
||||
assert.is.truthy(matcher.match('vi', 'void#') >= 1)
|
||||
assert.is.truthy(matcher.match('vo', 'void#') >= 1)
|
||||
assert.is.truthy(matcher.match('var_', 'var_dump') >= 1)
|
||||
assert.is.truthy(matcher.match('conso', 'console') > matcher.match('conso', 'ConstantSourceNode'))
|
||||
assert.is.truthy(matcher.match('usela', 'useLayoutEffect') > matcher.match('usela', 'useDataLayer'))
|
||||
assert.is.truthy(matcher.match('true', 'v:true', { 'true' }) == matcher.match('true', 'true'))
|
||||
assert.is.truthy(matcher.match('g', 'get', { 'get' }) > matcher.match('g', 'dein#get', { 'dein#get' }))
|
||||
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('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' }))
|
||||
end)
|
||||
|
||||
it('disallow_fuzzy_matching', function()
|
||||
assert.is.truthy(matcher.match('fmodify', 'fnamemodify', { disallow_fuzzy_matching = true }) == 0)
|
||||
assert.is.truthy(matcher.match('fmodify', 'fnamemodify', { disallow_fuzzy_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)
|
||||
assert.is.truthy(matcher.match('fb', 'fboo_bar', { disallow_partial_matching = true }) >= 1)
|
||||
assert.is.truthy(matcher.match('fb', 'fboo_bar', { disallow_partial_matching = false }) >= 1)
|
||||
end)
|
||||
|
||||
it('disallow_prefix_unmatching', function()
|
||||
assert.is.truthy(matcher.match('bar', 'foo_bar', { disallow_prefix_unmatching = true }) == 0)
|
||||
assert.is.truthy(matcher.match('bar', 'foo_bar', { disallow_prefix_unmatching = false }) >= 1)
|
||||
end)
|
||||
|
||||
it('debug', function()
|
||||
|
@ -15,12 +15,13 @@ local char = require('cmp.utils.char')
|
||||
---@field public source any
|
||||
---@field public cache cmp.Cache
|
||||
---@field public revision number
|
||||
---@field public context cmp.Context
|
||||
---@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 context cmp.Context
|
||||
---@field public completion_context lsp.CompletionContext|nil
|
||||
---@field public status cmp.SourceStatus
|
||||
---@field public complete_dedup function
|
||||
local source = {}
|
||||
@ -50,21 +51,28 @@ source.reset = function(self)
|
||||
self.cache:clear()
|
||||
self.revision = self.revision + 1
|
||||
self.context = context.empty()
|
||||
self.request_offset = -1
|
||||
self.is_triggered_by_symbol = false
|
||||
self.incomplete = false
|
||||
self.entries = {}
|
||||
self.offset = -1
|
||||
self.request_offset = -1
|
||||
self.completion_context = nil
|
||||
self.status = source.SourceStatus.WAITING
|
||||
self.complete_dedup(function() end)
|
||||
end
|
||||
|
||||
---Return source option
|
||||
---Return source config
|
||||
---@return cmp.SourceConfig
|
||||
source.get_config = function(self)
|
||||
source.get_source_config = function(self)
|
||||
return config.get_source_config(self.name) or {}
|
||||
end
|
||||
|
||||
---Return matching config
|
||||
---@return cmp.MatchingConfig
|
||||
source.get_matching_config = function()
|
||||
return config.get().matching
|
||||
end
|
||||
|
||||
---Get fetching time
|
||||
source.get_fetching_time = function(self)
|
||||
if self.status == source.SourceStatus.FETCHING then
|
||||
@ -101,7 +109,7 @@ source.get_entries = function(self, ctx)
|
||||
inputs[o] = string.sub(ctx.cursor_before_line, o)
|
||||
end
|
||||
|
||||
local match = e:match(inputs[o])
|
||||
local match = e:match(inputs[o], self:get_matching_config())
|
||||
e.score = match.score
|
||||
e.exact = false
|
||||
if e.score >= 1 then
|
||||
@ -112,7 +120,7 @@ source.get_entries = function(self, ctx)
|
||||
end
|
||||
self.cache:set({ 'get_entries', self.revision, ctx.cursor_before_line }, entries)
|
||||
|
||||
local max_item_count = self:get_config().max_item_count or 200
|
||||
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)
|
||||
@ -183,17 +191,33 @@ source.is_available = function(self)
|
||||
return true
|
||||
end
|
||||
|
||||
---Get trigger_characters
|
||||
---@return string[]
|
||||
source.get_trigger_characters = function(self)
|
||||
local c = self:get_source_config()
|
||||
if c.trigger_characters then
|
||||
return c.trigger_characters
|
||||
end
|
||||
|
||||
local trigger_characters = {}
|
||||
if self.source.get_trigger_characters then
|
||||
trigger_characters = self.source:get_trigger_characters(misc.copy(c)) or {}
|
||||
end
|
||||
if config.get().completion.get_trigger_characters then
|
||||
return config.get().completion.get_trigger_characters(trigger_characters)
|
||||
end
|
||||
return trigger_characters
|
||||
end
|
||||
|
||||
---Get keyword_pattern
|
||||
---@return string
|
||||
source.get_keyword_pattern = function(self)
|
||||
local c = self:get_config()
|
||||
local c = self:get_source_config()
|
||||
if c.keyword_pattern then
|
||||
return c.keyword_pattern
|
||||
end
|
||||
if self.source.get_keyword_pattern then
|
||||
return self.source:get_keyword_pattern({
|
||||
option = self:get_config().opts,
|
||||
})
|
||||
return self.source:get_keyword_pattern(misc.copy(c))
|
||||
end
|
||||
return config.get().completion.keyword_pattern
|
||||
end
|
||||
@ -201,48 +225,28 @@ end
|
||||
---Get keyword_length
|
||||
---@return number
|
||||
source.get_keyword_length = function(self)
|
||||
local c = self:get_config()
|
||||
local c = self:get_source_config()
|
||||
if c.keyword_length then
|
||||
return c.keyword_length
|
||||
end
|
||||
return config.get().completion.keyword_length or 1
|
||||
end
|
||||
|
||||
---Get trigger_characters
|
||||
---@return string[]
|
||||
source.get_trigger_characters = function(self)
|
||||
local trigger_characters = {}
|
||||
if self.source.get_trigger_characters then
|
||||
trigger_characters = self.source:get_trigger_characters({
|
||||
option = self:get_config().opts,
|
||||
}) or {}
|
||||
end
|
||||
if config.get().completion.get_trigger_characters then
|
||||
return config.get().completion.get_trigger_characters(trigger_characters)
|
||||
end
|
||||
return trigger_characters
|
||||
end
|
||||
|
||||
---Invoke completion
|
||||
---@param ctx cmp.Context
|
||||
---@param callback function
|
||||
---@return boolean Return true if not trigger completion.
|
||||
source.complete = function(self, ctx, callback)
|
||||
local offset = ctx:get_offset(self:get_keyword_pattern())
|
||||
if ctx.cursor.col <= offset then
|
||||
self:reset()
|
||||
end
|
||||
|
||||
-- NOTE: This implementation is nvim-cmp specific.
|
||||
-- We trigger new completion after core.confirm but we check only the symbol trigger_character in this case.
|
||||
local before_char = string.sub(ctx.cursor_before_line, -1)
|
||||
local before_char_iw = string.match(ctx.cursor_before_line, '(.)%s*$') or before_char
|
||||
|
||||
if ctx:get_reason() == types.cmp.ContextReason.TriggerOnly then
|
||||
if string.match(before_char, '^%a+$') then
|
||||
before_char = string.match(ctx.cursor_before_line, '(.)%s*$')
|
||||
if not before_char or not char.is_symbol(string.byte(before_char)) then
|
||||
before_char = ''
|
||||
end
|
||||
if string.match(before_char_iw, '^%a+$') then
|
||||
before_char_iw = ''
|
||||
end
|
||||
end
|
||||
|
||||
local completion_context
|
||||
@ -250,39 +254,31 @@ source.complete = function(self, ctx, callback)
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
||||
}
|
||||
else
|
||||
if vim.tbl_contains(self:get_trigger_characters(), before_char) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
||||
triggerCharacter = before_char,
|
||||
}
|
||||
elseif vim.tbl_contains(self:get_trigger_characters(), before_char_iw) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
||||
triggerCharacter = before_char_iw,
|
||||
}
|
||||
elseif ctx:get_reason() ~= types.cmp.ContextReason.TriggerOnly then
|
||||
if self:get_keyword_length() <= (ctx.cursor.col - offset) then
|
||||
if self.incomplete and self.context.cursor.col ~= ctx.cursor.col then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerForIncompleteCompletions,
|
||||
}
|
||||
elseif not vim.tbl_contains({ self.request_offset, self.offset }, offset) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
||||
}
|
||||
end
|
||||
elseif vim.tbl_contains(self:get_trigger_characters(), before_char) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
||||
triggerCharacter = before_char,
|
||||
}
|
||||
elseif ctx:get_reason() ~= types.cmp.ContextReason.TriggerOnly then
|
||||
if 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,
|
||||
}
|
||||
elseif not vim.tbl_contains({ self.request_offset, self.offset }, offset) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
||||
}
|
||||
end
|
||||
else
|
||||
self:reset()
|
||||
self:reset() -- Should clear current completion if the TriggerKind isn't TriggerCharacter or Manual and keyword length does not enough.
|
||||
end
|
||||
else
|
||||
self:reset() -- Should clear current completion if ContextReason is TriggerOnly and the triggerCharacter isn't matched
|
||||
end
|
||||
|
||||
-- Does not perform completions.
|
||||
if not completion_context then
|
||||
if ctx:get_reason() == types.cmp.ContextReason.TriggerOnly then
|
||||
self:reset()
|
||||
end
|
||||
debug.log(self:get_debug_name(), 'skip completion')
|
||||
return
|
||||
end
|
||||
|
||||
@ -293,16 +289,16 @@ source.complete = function(self, ctx, callback)
|
||||
debug.log(self:get_debug_name(), 'request', offset, vim.inspect(completion_context))
|
||||
local prev_status = self.status
|
||||
self.status = source.SourceStatus.FETCHING
|
||||
self.request_offset = offset
|
||||
self.offset = offset
|
||||
self.request_offset = offset
|
||||
self.context = ctx
|
||||
self.completion_context = completion_context
|
||||
self.source:complete(
|
||||
{
|
||||
context = ctx,
|
||||
vim.tbl_extend('keep', misc.copy(self:get_source_config()), {
|
||||
offset = self.offset,
|
||||
option = self:get_config().opts,
|
||||
context = ctx,
|
||||
completion_context = completion_context,
|
||||
},
|
||||
}),
|
||||
self.complete_dedup(vim.schedule_wrap(function(response)
|
||||
response = response or {}
|
||||
|
||||
@ -329,8 +325,9 @@ source.complete = function(self, ctx, callback)
|
||||
self.revision = self.revision + 1
|
||||
end
|
||||
else
|
||||
debug.log(self:get_debug_name(), 'continue', 'nil')
|
||||
if completion_context.triggerKind == types.lsp.CompletionTriggerKind.TriggerCharacter then
|
||||
-- The completion will be invoked when pressing <CR> if the trigger characters contain the <Space>.
|
||||
-- If the server returns an empty response in such a case, should invoke the keyword completion on the next keypress.
|
||||
if offset == ctx.cursor.col then
|
||||
self:reset()
|
||||
end
|
||||
self.status = prev_status
|
||||
|
@ -38,6 +38,7 @@ cmp.ItemField.Menu = 'menu'
|
||||
|
||||
---@class cmp.ConfirmOption
|
||||
---@field public behavior cmp.ConfirmBehavior
|
||||
---@field public commit_character? string
|
||||
|
||||
---@class cmp.SelectOption
|
||||
---@field public behavior cmp.SelectBehavior
|
||||
@ -46,18 +47,21 @@ cmp.ItemField.Menu = 'menu'
|
||||
---@field public body string
|
||||
---@field public insert_text_mode number
|
||||
|
||||
---@class cmp.CompleteParams
|
||||
---@field public reason? cmp.ContextReason
|
||||
---@field public config? cmp.ConfigSchema
|
||||
|
||||
---@class cmp.Setup
|
||||
---@field public __call fun(c: cmp.ConfigSchema)
|
||||
---@field public buffer fun(c: cmp.ConfigSchema)
|
||||
---@field public global fun(c: cmp.ConfigSchema)
|
||||
---@field public cmdline fun(type: string, c: cmp.ConfigSchema)
|
||||
|
||||
---@class cmp.SourceBaseApiParams
|
||||
---@field public option table
|
||||
---@class cmp.SourceApiParams: cmp.SourceConfig
|
||||
|
||||
---@class cmp.SourceCompletionApiParams : cmp.SourceBaseApiParams
|
||||
---@field public context cmp.Context
|
||||
---@class cmp.SourceCompletionApiParams : cmp.SourceConfig
|
||||
---@field public offset number
|
||||
---@field public context cmp.Context
|
||||
---@field public completion_context lsp.CompletionContext
|
||||
|
||||
---@class cmp.Mapping
|
||||
@ -73,11 +77,13 @@ cmp.ItemField.Menu = 'menu'
|
||||
---@field public completion cmp.CompletionConfig
|
||||
---@field public documentation cmp.DocumentationConfig|"false"
|
||||
---@field public confirmation cmp.ConfirmationConfig
|
||||
---@field public matching cmp.MatchingConfig
|
||||
---@field public sorting cmp.SortingConfig
|
||||
---@field public formatting cmp.FormattingConfig
|
||||
---@field public snippet cmp.SnippetConfig
|
||||
---@field public mapping table<string, cmp.Mapping>
|
||||
---@field public sources cmp.SourceConfig[]
|
||||
---@field public view cmp.ViewConfig
|
||||
---@field public experimental cmp.ExperimentalConfig
|
||||
|
||||
---@class cmp.CompletionConfig
|
||||
@ -98,6 +104,11 @@ cmp.ItemField.Menu = 'menu'
|
||||
---@field public default_behavior cmp.ConfirmBehavior
|
||||
---@field public get_commit_characters fun(commit_characters: string[]): string[]
|
||||
|
||||
---@class cmp.MatchingConfig
|
||||
---@field public disallow_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 comparators function[]
|
||||
@ -118,11 +129,28 @@ cmp.ItemField.Menu = 'menu'
|
||||
|
||||
---@class cmp.SourceConfig
|
||||
---@field public name string
|
||||
---@field public opts table
|
||||
---@field public option table|nil
|
||||
---@field public priority number|nil
|
||||
---@field public keyword_pattern string
|
||||
---@field public keyword_length number
|
||||
---@field public max_item_count number
|
||||
---@field public group_index number
|
||||
---@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
|
||||
|
||||
---@class cmp.ViewConfig
|
||||
---@field public entries cmp.EntriesConfig
|
||||
|
||||
---@alias cmp.EntriesConfig cmp.CustomEntriesConfig|cmp.NativeEntriesConfig|cmp.WildmenuEntriesConfig|string
|
||||
|
||||
---@class cmp.CustomEntriesConfig
|
||||
---@field name "'custom'"
|
||||
---@field selection_order "'top_down'"|"'near_cursor'"
|
||||
|
||||
---@class cmp.NativeEntriesConfig
|
||||
---@field name "'native'"
|
||||
|
||||
---@class cmp.WildmenuEntriesConfig
|
||||
---@field name "'wildmenu'"
|
||||
---@field separator string|nil
|
||||
|
||||
return cmp
|
||||
|
@ -26,7 +26,7 @@ lsp.Position.to_vim = function(buf, position)
|
||||
}
|
||||
end
|
||||
|
||||
---Convert lsp.Position to vim.Position
|
||||
---Convert vim.Position to lsp.Position
|
||||
---@param buf number|string
|
||||
---@param position vim.Position
|
||||
---@return lsp.Position
|
||||
@ -49,7 +49,7 @@ end
|
||||
|
||||
lsp.Range = {}
|
||||
|
||||
---Convert lsp.Position to vim.Position
|
||||
---Convert lsp.Range to vim.Range
|
||||
---@param buf number|string
|
||||
---@param range lsp.Range
|
||||
---@return vim.Range
|
||||
@ -60,7 +60,7 @@ lsp.Range.to_vim = function(buf, range)
|
||||
}
|
||||
end
|
||||
|
||||
---Convert lsp.Position to vim.Position
|
||||
---Convert vim.Range to lsp.Range
|
||||
---@param buf number|string
|
||||
---@param range vim.Range
|
||||
---@return lsp.Range
|
||||
@ -97,7 +97,6 @@ lsp.InsertTextMode = vim.tbl_add_reverse_lookup(lsp.InsertTextMode)
|
||||
lsp.MarkupKind = {}
|
||||
lsp.MarkupKind.PlainText = 'plaintext'
|
||||
lsp.MarkupKind.Markdown = 'markdown'
|
||||
lsp.MarkupKind.Markdown = 'markdown'
|
||||
lsp.MarkupKind = vim.tbl_add_reverse_lookup(lsp.MarkupKind)
|
||||
|
||||
---@alias lsp.CompletionItemTag "1"
|
||||
|
@ -11,35 +11,36 @@ describe('types.lsp', function()
|
||||
})
|
||||
local vim_position, lsp_position
|
||||
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 3 })
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
vim_position = lsp.Position.to_vim(bufnr, { line = 1, character = 3 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 10)
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
lsp_position = lsp.Position.to_lsp(bufnr, vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 3)
|
||||
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 0 })
|
||||
vim_position = lsp.Position.to_vim(bufnr, { line = 1, character = 0 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 1)
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
lsp_position = lsp.Position.to_lsp(bufnr, vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 0)
|
||||
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 5 })
|
||||
vim_position = lsp.Position.to_vim(bufnr, { line = 1, character = 5 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 16)
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
lsp_position = lsp.Position.to_lsp(bufnr, vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 5)
|
||||
|
||||
-- overflow (lsp -> vim)
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 6 })
|
||||
vim_position = lsp.Position.to_vim(bufnr, { line = 1, character = 6 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 16)
|
||||
|
||||
-- overflow(vim -> lsp)
|
||||
vim_position.col = vim_position.col + 1
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
lsp_position = lsp.Position.to_lsp(bufnr, vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 5)
|
||||
end)
|
||||
|
@ -7,6 +7,9 @@
|
||||
---@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
|
||||
|
@ -53,7 +53,8 @@ end
|
||||
|
||||
api.get_screen_cursor = function()
|
||||
if api.is_cmdline_mode() then
|
||||
return api.get_cursor()
|
||||
local cursor = api.get_cursor()
|
||||
return { cursor[1], cursor[2] + 1 }
|
||||
end
|
||||
local cursor = api.get_cursor()
|
||||
local pos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1)
|
||||
|
@ -1,7 +1,9 @@
|
||||
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 stop function
|
||||
---@field public __call function
|
||||
|
||||
@ -12,7 +14,13 @@ async.throttle = function(fn, timeout)
|
||||
local time = nil
|
||||
local timer = vim.loop.new_timer()
|
||||
return setmetatable({
|
||||
running = false,
|
||||
timeout = timeout,
|
||||
sync = function(self, timeout_)
|
||||
vim.wait(timeout_ or 1000, function()
|
||||
return not self.running
|
||||
end)
|
||||
end,
|
||||
stop = function()
|
||||
time = nil
|
||||
timer:stop()
|
||||
@ -24,12 +32,15 @@ async.throttle = function(fn, timeout)
|
||||
if time == nil then
|
||||
time = vim.loop.now()
|
||||
end
|
||||
timer:stop()
|
||||
|
||||
local delta = math.max(1, self.timeout - (vim.loop.now() - time))
|
||||
timer:start(delta, 0, function()
|
||||
time = nil
|
||||
fn(unpack(args))
|
||||
self.running = true
|
||||
timer:stop()
|
||||
timer:start(math.max(1, self.timeout - (vim.loop.now() - time)), 0, function()
|
||||
vim.schedule(function()
|
||||
time = nil
|
||||
fn(unpack(args))
|
||||
self.running = false
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
@ -1,17 +1,30 @@
|
||||
local buffer = {}
|
||||
|
||||
buffer.ensure = setmetatable({
|
||||
cache = {},
|
||||
}, {
|
||||
__call = function(self, name)
|
||||
if not (self.cache[name] and vim.api.nvim_buf_is_valid(self.cache[name])) then
|
||||
local 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')
|
||||
self.cache[name] = buf
|
||||
end
|
||||
return self.cache[name]
|
||||
end,
|
||||
})
|
||||
buffer.cache = {}
|
||||
|
||||
---@return number buf
|
||||
buffer.get = function(name)
|
||||
local buf = buffer.cache[name]
|
||||
if buf and vim.api.nvim_buf_is_valid(buf) then
|
||||
return buf
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
---@return number buf
|
||||
---@return boolean created_new
|
||||
buffer.ensure = function(name)
|
||||
local created_new = false
|
||||
local buf = buffer.get(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
|
||||
end
|
||||
|
||||
return buffer
|
||||
|
@ -23,15 +23,6 @@ describe('feedkeys', function()
|
||||
})
|
||||
end)
|
||||
|
||||
it('autoindent', function()
|
||||
vim.cmd([[setlocal indentkeys+==end]])
|
||||
feedkeys.call(keymap.t('iif<CR><Tab>end') .. keymap.autoindent(), 'nx')
|
||||
assert.are.same(vim.api.nvim_buf_get_lines(0, 0, -1, false), {
|
||||
'if',
|
||||
'end',
|
||||
})
|
||||
end)
|
||||
|
||||
it('testability', function()
|
||||
feedkeys.call('i', 'n', function()
|
||||
feedkeys.call('', 'n', function()
|
||||
|
@ -10,7 +10,7 @@ highlight.keys = {
|
||||
}
|
||||
|
||||
highlight.inherit = function(name, source, override)
|
||||
local cmd = ('highlight! default %s'):format(name)
|
||||
local cmd = ('highlight default %s'):format(name)
|
||||
for _, key in ipairs(highlight.keys) do
|
||||
if override[key] then
|
||||
cmd = cmd .. (' %s=%s'):format(key, override[key])
|
||||
|
@ -7,7 +7,9 @@ local keymap = {}
|
||||
---@param keys string
|
||||
---@return string
|
||||
keymap.t = function(keys)
|
||||
return vim.api.nvim_replace_termcodes(keys, true, true, true)
|
||||
return (string.gsub(keys, '(<[A-Za-z0-9\\%-%[%]%^@]->)', function(match)
|
||||
return vim.api.nvim_eval(string.format([["\%s"]], match))
|
||||
end))
|
||||
end
|
||||
|
||||
---Normalize key sequence.
|
||||
@ -65,6 +67,9 @@ end
|
||||
---@param count number
|
||||
---@return string
|
||||
keymap.backspace = function(count)
|
||||
if type(count) == 'string' then
|
||||
count = vim.fn.strchars(count, true)
|
||||
end
|
||||
if count <= 0 then
|
||||
return ''
|
||||
end
|
||||
@ -73,16 +78,11 @@ keymap.backspace = function(count)
|
||||
return table.concat(keys, '')
|
||||
end
|
||||
|
||||
---Create autoindent keys
|
||||
---Update indentkeys.
|
||||
---@param expr string
|
||||
---@return string
|
||||
keymap.autoindent = function()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal cindent<CR>'))
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal indentkeys+=!^F<CR>'))
|
||||
table.insert(keys, keymap.t('<C-f>'))
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal %scindent<CR>'):format(vim.bo.cindent and '' or 'no'))
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal indentkeys=%s<CR>'):format(vim.bo.indentkeys:gsub('|', '\\|')))
|
||||
return table.concat(keys, '')
|
||||
keymap.indentkeys = function(expr)
|
||||
return string.format(keymap.t('<Cmd>set indentkeys=%s<CR>'), expr and vim.fn.escape(expr, '| \t\\') or '')
|
||||
end
|
||||
|
||||
---Return two key sequence are equal or not.
|
||||
@ -97,25 +97,22 @@ end
|
||||
keymap.listen = function(mode, lhs, callback)
|
||||
lhs = keymap.normalize(keymap.to_keymap(lhs))
|
||||
|
||||
local existing = keymap.get_mapping(mode, 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
|
||||
return
|
||||
end
|
||||
|
||||
local bufnr = existing.buffer and vim.api.nvim_get_current_buf() or -1
|
||||
local fallback = keymap.evacuate(bufnr, mode, lhs)
|
||||
local fallback = keymap.fallback(bufnr, mode, existing)
|
||||
keymap.set_map(bufnr, mode, lhs, function()
|
||||
if mode == 'c' and vim.fn.getcmdtype() == '=' then
|
||||
return vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true)
|
||||
local ignore = false
|
||||
ignore = ignore or (mode == 'c' and vim.fn.getcmdtype() == '=')
|
||||
if ignore then
|
||||
fallback()
|
||||
else
|
||||
callback(lhs, misc.once(fallback))
|
||||
end
|
||||
|
||||
callback(
|
||||
lhs,
|
||||
misc.once(function()
|
||||
vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true)
|
||||
end)
|
||||
)
|
||||
end, {
|
||||
expr = false,
|
||||
noremap = true,
|
||||
@ -123,19 +120,66 @@ keymap.listen = function(mode, lhs, callback)
|
||||
})
|
||||
end
|
||||
|
||||
---Get mapping
|
||||
---Fallback
|
||||
keymap.fallback = function(bufnr, mode, map)
|
||||
return function()
|
||||
if map.expr then
|
||||
local fallback_expr = string.format('<Plug>(cmp.u.k.fallback_expr:%s)', map.lhs)
|
||||
keymap.set_map(bufnr, mode, fallback_expr, function()
|
||||
return keymap.solve(bufnr, mode, map).keys
|
||||
end, {
|
||||
expr = true,
|
||||
noremap = map.noremap,
|
||||
script = map.script,
|
||||
nowait = map.nowait,
|
||||
silent = map.silent and mode ~= 'c',
|
||||
})
|
||||
vim.api.nvim_feedkeys(keymap.t(fallback_expr), 'im', true)
|
||||
elseif not map.callback then
|
||||
local solved = keymap.solve(bufnr, mode, map)
|
||||
vim.api.nvim_feedkeys(solved.keys, solved.mode, true)
|
||||
else
|
||||
map.callback()
|
||||
end
|
||||
end
|
||||
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)
|
||||
|
||||
if map.noremap then
|
||||
return { keys = rhs, mode = 'in' }
|
||||
end
|
||||
|
||||
if string.find(rhs, lhs, 1, true) == 1 then
|
||||
local recursive = string.format('<SNR>0_(cmp.u.k.recursive:%s)', lhs)
|
||||
keymap.set_map(bufnr, mode, recursive, lhs, {
|
||||
noremap = true,
|
||||
script = map.script,
|
||||
nowait = map.nowait,
|
||||
silent = map.silent and mode ~= 'c',
|
||||
})
|
||||
return { keys = keymap.t(recursive) .. string.gsub(rhs, '^' .. vim.pesc(lhs), ''), mode = 'im' }
|
||||
end
|
||||
return { keys = rhs, mode = 'im' }
|
||||
end
|
||||
|
||||
---Get map
|
||||
---@param mode string
|
||||
---@param lhs string
|
||||
---@return table
|
||||
keymap.get_mapping = function(mode, lhs)
|
||||
keymap.get_map = function(mode, lhs)
|
||||
lhs = keymap.normalize(lhs)
|
||||
|
||||
for _, map in ipairs(vim.api.nvim_buf_get_keymap(0, mode)) do
|
||||
if keymap.equals(map.lhs, lhs) then
|
||||
return {
|
||||
lhs = map.lhs,
|
||||
rhs = map.rhs,
|
||||
rhs = map.rhs or '',
|
||||
expr = map.expr == 1,
|
||||
callback = map.callback,
|
||||
noremap = map.noremap == 1,
|
||||
script = map.script == 1,
|
||||
silent = map.silent == 1,
|
||||
@ -149,8 +193,9 @@ keymap.get_mapping = function(mode, lhs)
|
||||
if keymap.equals(map.lhs, lhs) then
|
||||
return {
|
||||
lhs = map.lhs,
|
||||
rhs = map.rhs,
|
||||
rhs = map.rhs or '',
|
||||
expr = map.expr == 1,
|
||||
callback = map.callback,
|
||||
noremap = map.noremap == 1,
|
||||
script = map.script == 1,
|
||||
silent = map.silent == 1,
|
||||
@ -164,80 +209,15 @@ keymap.get_mapping = function(mode, lhs)
|
||||
lhs = lhs,
|
||||
rhs = lhs,
|
||||
expr = false,
|
||||
callback = nil,
|
||||
noremap = true,
|
||||
script = false,
|
||||
silent = false,
|
||||
silent = true,
|
||||
nowait = false,
|
||||
buffer = false,
|
||||
}
|
||||
end
|
||||
|
||||
---Evacuate existing key mapping
|
||||
---@param bufnr number
|
||||
---@param mode string
|
||||
---@param lhs string
|
||||
---@return { keys: string, mode: string }
|
||||
keymap.evacuate = function(bufnr, mode, lhs)
|
||||
local map = keymap.get_mapping(mode, lhs)
|
||||
if not map then
|
||||
return { keys = lhs, mode = 'itn' }
|
||||
end
|
||||
|
||||
-- Keep existing mapping as <Plug> mapping. We escape fisrt recursive key sequence. See `:help recursive_mapping`)
|
||||
local rhs = map.rhs
|
||||
if not map.noremap and map.expr then
|
||||
-- remap & expr mapping should evacuate as <Plug> mapping with solving recursive mapping.
|
||||
rhs = function()
|
||||
return keymap.t(keymap.recursive(bufnr, mode, lhs, vim.api.nvim_eval(map.rhs)))
|
||||
end
|
||||
elseif map.noremap and map.expr then
|
||||
-- noremap & expr mapping should always evacuate as <Plug> mapping.
|
||||
rhs = rhs
|
||||
elseif map.script then
|
||||
-- script mapping should always evacuate as <Plug> mapping.
|
||||
rhs = rhs
|
||||
elseif not map.noremap then
|
||||
-- remap & non-expr mapping should be checked if recursive or not.
|
||||
rhs = keymap.recursive(bufnr, mode, lhs, rhs)
|
||||
if keymap.equals(rhs, map.rhs) or map.noremap then
|
||||
return { keys = rhs, mode = 'it' .. (map.noremap and 'n' or '') }
|
||||
end
|
||||
else
|
||||
-- noremap & non-expr mapping doesn't need to evacuate.
|
||||
return { keys = rhs, mode = 'it' .. (map.noremap and 'n' or '') }
|
||||
end
|
||||
|
||||
local fallback = ('<Plug>(cmp.utils.keymap.evacuate:%s)'):format(map.lhs)
|
||||
keymap.set_map(bufnr, mode, fallback, rhs, {
|
||||
expr = map.expr,
|
||||
noremap = map.noremap,
|
||||
script = map.script,
|
||||
silent = mode ~= 'c', -- I can't understand but it solves the #427 (wilder.nvim's mapping does not work if silent=true in cmdline mode...)
|
||||
})
|
||||
return { keys = fallback, mode = 'it' }
|
||||
end
|
||||
|
||||
---Solve recursive mapping
|
||||
---@param bufnr number
|
||||
---@param mode string
|
||||
---@param lhs string
|
||||
---@param rhs string
|
||||
---@return string
|
||||
keymap.recursive = function(bufnr, mode, lhs, rhs)
|
||||
rhs = keymap.normalize(rhs)
|
||||
|
||||
local recursive_lhs = ('<Plug>(cmp.utils.keymap.recursive:%s)'):format(lhs)
|
||||
local recursive_rhs = string.gsub(rhs, '^' .. vim.pesc(keymap.normalize(lhs)), recursive_lhs)
|
||||
if not keymap.equals(recursive_rhs, rhs) then
|
||||
keymap.set_map(bufnr, mode, recursive_lhs, lhs, {
|
||||
expr = false,
|
||||
noremap = true,
|
||||
silent = true,
|
||||
})
|
||||
end
|
||||
return recursive_rhs
|
||||
end
|
||||
|
||||
---Set keymapping
|
||||
keymap.set_map = setmetatable({
|
||||
callbacks = {},
|
||||
|
@ -1,68 +1,159 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
local api = require('cmp.utils.api')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
|
||||
describe('keymap', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('t', function()
|
||||
for _, key in ipairs({
|
||||
'<F1>',
|
||||
'<C-a>',
|
||||
'<C-]>',
|
||||
'<C-[>',
|
||||
'<C-^>',
|
||||
'<C-@>',
|
||||
'<C-\\>',
|
||||
'<Tab>',
|
||||
'<S-Tab>',
|
||||
'<Plug>(example)',
|
||||
'<C-r>="abc"<CR>',
|
||||
'<Cmd>normal! ==<CR>',
|
||||
}) do
|
||||
assert.are.equal(keymap.t(key), vim.api.nvim_replace_termcodes(key, true, true, true))
|
||||
assert.are.equal(keymap.t(key .. key), vim.api.nvim_replace_termcodes(key .. key, true, true, true))
|
||||
assert.are.equal(keymap.t(key .. key .. key), vim.api.nvim_replace_termcodes(key .. key .. key, true, true, true))
|
||||
end
|
||||
end)
|
||||
|
||||
it('to_keymap', function()
|
||||
assert.are.equal(keymap.to_keymap('\n'), '<CR>')
|
||||
assert.are.equal(keymap.to_keymap('<CR>'), '<CR>')
|
||||
assert.are.equal(keymap.to_keymap('|'), '<Bar>')
|
||||
end)
|
||||
|
||||
describe('evacuate', function()
|
||||
describe('fallback', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('expr & register', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', [['<C-r>="("<CR>']], {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
})
|
||||
local fallback = keymap.evacuate(0, 'i', '(')
|
||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
||||
assert.are.same({ '(' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
it('recursive & <Plug> (tpope/vim-endwise)', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Plug>(paren-close)', [[)<Left>]], {
|
||||
expr = false,
|
||||
noremap = true,
|
||||
})
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', [[(<Plug>(paren-close)]], {
|
||||
expr = false,
|
||||
noremap = false,
|
||||
})
|
||||
local fallback = keymap.evacuate(0, 'i', '(')
|
||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
||||
assert.are.same({ '()' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
describe('expr & recursive', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('true', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Tab>', [[v:true ? '<C-r>="foobar"<CR>' : '<Tab>aiueo']], {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
})
|
||||
local fallback = keymap.evacuate(0, 'i', '<Tab>')
|
||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
||||
assert.are.same({ 'foobar' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
local run_fallback = function(keys, fallback)
|
||||
local state = {}
|
||||
feedkeys.call(keys, '', function()
|
||||
fallback()
|
||||
end)
|
||||
it('false', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Tab>', [[v:false ? '<C-r>="foobar"<CR>' : '<Tab>aiueo']], {
|
||||
feedkeys.call('', '', function()
|
||||
if api.is_cmdline_mode() then
|
||||
state.buffer = { api.get_current_line() }
|
||||
else
|
||||
state.buffer = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
end
|
||||
state.cursor = api.get_cursor()
|
||||
end)
|
||||
feedkeys.call('', 'x')
|
||||
return state
|
||||
end
|
||||
|
||||
describe('basic', function()
|
||||
it('<Plug>', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Plug>(pairs)', '()<Left>', { noremap = true })
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '<Plug>(pairs)', { noremap = false })
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('<C-r>=', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '<C-r>="()"<CR><Left>', {})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('callback', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '', {
|
||||
callback = function()
|
||||
vim.api.nvim_feedkeys('()' .. keymap.t('<Left>'), 'int', true)
|
||||
end,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('expr-callback', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '', {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
callback = function()
|
||||
return '()' .. keymap.t('<Left>')
|
||||
end,
|
||||
})
|
||||
local fallback = keymap.evacuate(0, 'i', '<Tab>')
|
||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
||||
assert.are.same({ '\taiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
-- it('cmdline default <Tab>', function()
|
||||
-- local fallback = keymap.fallback(0, 'c', keymap.get_map('c', '<Tab>'))
|
||||
-- local state = run_fallback(':', fallback)
|
||||
-- assert.are.same({ '' }, state.buffer)
|
||||
-- assert.are.same({ 1, 0 }, state.cursor)
|
||||
-- end)
|
||||
end)
|
||||
|
||||
describe('recursive', function()
|
||||
it('non-expr', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '()<Left>', {
|
||||
expr = false,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('expr', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '"()<Left>"', {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('expr-callback', function()
|
||||
pcall(function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '', {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
callback = function()
|
||||
return keymap.t('()<Left>')
|
||||
end,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('realworld', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('#226', function()
|
||||
keymap.listen('i', '<c-n>', function(_, fallback)
|
||||
fallback()
|
||||
@ -70,6 +161,7 @@ describe('keymap', function()
|
||||
vim.api.nvim_feedkeys(keymap.t('iaiueo<CR>a<C-n><C-n>'), 'tx', true)
|
||||
assert.are.same({ 'aiueo', 'aiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
it('#414', function()
|
||||
keymap.listen('i', '<M-j>', function()
|
||||
vim.api.nvim_feedkeys(keymap.t('<C-n>'), 'int', true)
|
||||
@ -77,5 +169,19 @@ describe('keymap', function()
|
||||
vim.api.nvim_feedkeys(keymap.t('iaiueo<CR>a<M-j><M-j>'), 'tx', true)
|
||||
assert.are.same({ 'aiueo', 'aiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
it('#744', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<C-r>', 'recursive', {
|
||||
noremap = true,
|
||||
})
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<CR>', '<CR>recursive', {
|
||||
noremap = false,
|
||||
})
|
||||
keymap.listen('i', '<CR>', function(_, fallback)
|
||||
fallback()
|
||||
end)
|
||||
feedkeys.call(keymap.t('i<CR>'), 'tx')
|
||||
assert.are.same({ '', 'recursive' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@ -29,6 +29,28 @@ misc.concat = function(list1, list2)
|
||||
return new_list
|
||||
end
|
||||
|
||||
---Return the valu is empty or not.
|
||||
---@param v any
|
||||
---@return boolean
|
||||
misc.empty = function(v)
|
||||
if not v then
|
||||
return true
|
||||
end
|
||||
if v == vim.NIL then
|
||||
return true
|
||||
end
|
||||
if type(v) == 'string' and v == '' then
|
||||
return true
|
||||
end
|
||||
if type(v) == 'table' and vim.tbl_isempty(v) then
|
||||
return true
|
||||
end
|
||||
if type(v) == 'number' and v == 0 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---The symbol to remove key in misc.merge.
|
||||
misc.none = vim.NIL
|
||||
|
||||
@ -77,7 +99,7 @@ misc.id = setmetatable({
|
||||
group = {},
|
||||
}, {
|
||||
__call = function(_, group)
|
||||
misc.id.group[group] = misc.id.group[group] or vim.loop.now()
|
||||
misc.id.group[group] = misc.id.group[group] or 0
|
||||
misc.id.group[group] = misc.id.group[group] + 1
|
||||
return misc.id.group[group]
|
||||
end,
|
||||
@ -144,9 +166,10 @@ end
|
||||
|
||||
---Safe version of vim.str_utfindex
|
||||
---@param text string
|
||||
---@param vimindex number
|
||||
---@param vimindex number|nil
|
||||
---@return number
|
||||
misc.to_utfindex = function(text, vimindex)
|
||||
vimindex = vimindex or #text + 1
|
||||
return vim.str_utfindex(text, math.max(0, math.min(vimindex - 1, #text)))
|
||||
end
|
||||
|
||||
@ -155,6 +178,7 @@ end
|
||||
---@param utfindex number
|
||||
---@return number
|
||||
misc.to_vimindex = function(text, utfindex)
|
||||
utfindex = utfindex or #text
|
||||
for i = utfindex, 1, -1 do
|
||||
local s, v = pcall(function()
|
||||
return vim.str_byteindex(text, i) + 1
|
||||
@ -178,4 +202,34 @@ misc.deprecated = function(fn, msg)
|
||||
end
|
||||
end
|
||||
|
||||
--Redraw
|
||||
misc.redraw = setmetatable({
|
||||
doing = false,
|
||||
force = false,
|
||||
termcode = vim.api.nvim_replace_termcodes('<C-r><Esc>', true, true, true),
|
||||
}, {
|
||||
__call = function(self, force)
|
||||
if vim.tbl_contains({ '/', '?' }, vim.fn.getcmdtype()) then
|
||||
if vim.o.incsearch then
|
||||
return vim.api.nvim_feedkeys(self.termcode, 'in', true)
|
||||
end
|
||||
end
|
||||
|
||||
if self.doing then
|
||||
return
|
||||
end
|
||||
self.doing = true
|
||||
self.force = not not force
|
||||
vim.schedule(function()
|
||||
if self.force then
|
||||
vim.cmd([[redraw!]])
|
||||
else
|
||||
vim.cmd([[redraw]])
|
||||
end
|
||||
self.doing = false
|
||||
self.force = false
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
return misc
|
||||
|
@ -3,24 +3,29 @@ local pattern = require('cmp.utils.pattern')
|
||||
|
||||
local str = {}
|
||||
|
||||
local INVALID_CHARS = {}
|
||||
INVALID_CHARS[string.byte("'")] = true
|
||||
INVALID_CHARS[string.byte('"')] = true
|
||||
INVALID_CHARS[string.byte('=')] = true
|
||||
INVALID_CHARS[string.byte('$')] = true
|
||||
INVALID_CHARS[string.byte('(')] = true
|
||||
INVALID_CHARS[string.byte('[')] = true
|
||||
INVALID_CHARS[string.byte(' ')] = true
|
||||
INVALID_CHARS[string.byte('\t')] = true
|
||||
INVALID_CHARS[string.byte('\n')] = true
|
||||
INVALID_CHARS[string.byte('\r')] = true
|
||||
local INVALIDS = {}
|
||||
INVALIDS[string.byte("'")] = true
|
||||
INVALIDS[string.byte('"')] = true
|
||||
INVALIDS[string.byte('=')] = true
|
||||
INVALIDS[string.byte('$')] = true
|
||||
INVALIDS[string.byte('(')] = true
|
||||
INVALIDS[string.byte('[')] = true
|
||||
INVALIDS[string.byte('<')] = true
|
||||
INVALIDS[string.byte('{')] = true
|
||||
INVALIDS[string.byte(' ')] = true
|
||||
INVALIDS[string.byte('\t')] = true
|
||||
INVALIDS[string.byte('\n')] = true
|
||||
INVALIDS[string.byte('\r')] = true
|
||||
|
||||
local NR_BYTE = string.byte('\n')
|
||||
|
||||
local PAIR_CHARS = {}
|
||||
PAIR_CHARS[string.byte('[')] = string.byte(']')
|
||||
PAIR_CHARS[string.byte('(')] = string.byte(')')
|
||||
PAIR_CHARS[string.byte('<')] = string.byte('>')
|
||||
local PAIRS = {}
|
||||
PAIRS[string.byte('<')] = string.byte('>')
|
||||
PAIRS[string.byte('[')] = string.byte(']')
|
||||
PAIRS[string.byte('(')] = string.byte(')')
|
||||
PAIRS[string.byte('{')] = string.byte('}')
|
||||
PAIRS[string.byte('"')] = string.byte('"')
|
||||
PAIRS[string.byte("'")] = string.byte("'")
|
||||
|
||||
---Return if specified text has prefix or not
|
||||
---@param text string
|
||||
@ -38,6 +43,17 @@ str.has_prefix = function(text, prefix)
|
||||
return true
|
||||
end
|
||||
|
||||
---get_common_string
|
||||
str.get_common_string = function(text1, text2)
|
||||
local min = math.min(#text1, #text2)
|
||||
for i = 1, min do
|
||||
if not char.match(string.byte(text1, i), string.byte(text2, i)) then
|
||||
return string.sub(text1, 1, i - 1)
|
||||
end
|
||||
end
|
||||
return string.sub(text1, 1, min)
|
||||
end
|
||||
|
||||
---Remove suffix
|
||||
---@param text string
|
||||
---@param suffix string
|
||||
@ -101,23 +117,47 @@ end
|
||||
|
||||
---get_word
|
||||
---@param text string
|
||||
---@param stop_char number
|
||||
---@param min_length number
|
||||
---@return string
|
||||
str.get_word = function(text, stop_char)
|
||||
local valids = {}
|
||||
local has_valid = false
|
||||
for idx = 1, #text do
|
||||
local c = string.byte(text, idx)
|
||||
local invalid = INVALID_CHARS[c] and not (valids[c] and stop_char ~= c)
|
||||
if has_valid and invalid then
|
||||
return string.sub(text, 1, idx - 1)
|
||||
str.get_word = function(text, stop_char, min_length)
|
||||
min_length = min_length or 0
|
||||
|
||||
local has_alnum = false
|
||||
local stack = {}
|
||||
local word = {}
|
||||
local add = function(c)
|
||||
table.insert(word, string.char(c))
|
||||
if stack[#stack] == c then
|
||||
table.remove(stack, #stack)
|
||||
else
|
||||
if PAIRS[c] then
|
||||
table.insert(stack, c)
|
||||
end
|
||||
end
|
||||
valids[c] = true
|
||||
if PAIR_CHARS[c] then
|
||||
valids[PAIR_CHARS[c]] = true
|
||||
end
|
||||
has_valid = has_valid or not invalid
|
||||
end
|
||||
return text
|
||||
for i = 1, #text do
|
||||
local c = string.byte(text, i, i)
|
||||
if #word < min_length then
|
||||
table.insert(word, string.char(c))
|
||||
elseif not INVALIDS[c] then
|
||||
add(c)
|
||||
has_alnum = has_alnum or char.is_alnum(c)
|
||||
elseif not has_alnum then
|
||||
add(c)
|
||||
elseif #stack ~= 0 then
|
||||
add(c)
|
||||
if has_alnum and #stack == 0 then
|
||||
break
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
if stop_char and word[#word] == string.char(stop_char) then
|
||||
table.remove(word, #word)
|
||||
end
|
||||
return table.concat(word, '')
|
||||
end
|
||||
|
||||
---Oneline
|
||||
|
@ -7,6 +7,9 @@ describe('utils.str', function()
|
||||
assert.are.equal(str.get_word('print()'), 'print')
|
||||
assert.are.equal(str.get_word('["cmp#confirm"]'), '["cmp#confirm"]')
|
||||
assert.are.equal(str.get_word('"devDependencies":', string.byte('"')), '"devDependencies')
|
||||
assert.are.equal(str.get_word('"devDependencies": ${1},', string.byte('"')), '"devDependencies')
|
||||
assert.are.equal(str.get_word('#[cfg(test)]'), '#[cfg(test)]')
|
||||
assert.are.equal(str.get_word('import { GetStaticProps$1 } from "next";', nil, 9), 'import { GetStaticProps')
|
||||
end)
|
||||
|
||||
it('strikethrough', function()
|
||||
|
@ -18,6 +18,7 @@ local api = require('cmp.utils.api')
|
||||
---@field public swin2 number|nil
|
||||
---@field public style cmp.WindowStyle
|
||||
---@field public opt table<string, any>
|
||||
---@field public buffer_opt table<string, any>
|
||||
---@field public cache cmp.Cache
|
||||
local window = {}
|
||||
|
||||
@ -32,6 +33,7 @@ window.new = function()
|
||||
self.style = {}
|
||||
self.cache = cache.new()
|
||||
self.opt = {}
|
||||
self.buffer_opt = {}
|
||||
return self
|
||||
end
|
||||
|
||||
@ -54,6 +56,26 @@ window.option = function(self, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
---Set buffer option.
|
||||
---NOTE: If the buffer already visible, immediately applied to it.
|
||||
---@param key string
|
||||
---@param value any
|
||||
window.buffer_option = function(self, key, value)
|
||||
if vim.fn.exists('+' .. key) == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if value == nil then
|
||||
return self.buffer_opt[key]
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
---Set style.
|
||||
---@param style cmp.WindowStyle
|
||||
window.set_style = function(self, style)
|
||||
@ -70,7 +92,13 @@ end
|
||||
---Return buffer id.
|
||||
---@return number
|
||||
window.get_buffer = function(self)
|
||||
return buffer.ensure(self.name)
|
||||
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)
|
||||
end
|
||||
end
|
||||
return buf
|
||||
end
|
||||
|
||||
---Open window
|
||||
@ -89,7 +117,7 @@ window.open = function(self, style)
|
||||
else
|
||||
local s = misc.copy(self.style)
|
||||
s.noautocmd = true
|
||||
self.win = vim.api.nvim_open_win(buffer.ensure(self.name), false, s)
|
||||
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)
|
||||
end
|
||||
@ -148,7 +176,7 @@ window.update = function(self)
|
||||
-- In cmdline, vim does not redraw automatically.
|
||||
if api.is_cmdline_mode() then
|
||||
vim.api.nvim_win_call(self.win, function()
|
||||
vim.cmd([[redraw]])
|
||||
misc.redraw()
|
||||
end)
|
||||
end
|
||||
end
|
||||
@ -251,9 +279,14 @@ window.get_content_height = function(self)
|
||||
vim.api.nvim_buf_get_changedtick(self:get_buffer()),
|
||||
}, function()
|
||||
local height = 0
|
||||
for _, text in ipairs(vim.api.nvim_buf_get_lines(self:get_buffer(), 0, -1, false)) do
|
||||
height = height + math.ceil(math.max(1, vim.str_utfindex(text)) / self.style.width)
|
||||
end
|
||||
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
|
||||
end)
|
||||
end
|
||||
|
@ -4,6 +4,7 @@ local event = require('cmp.utils.event')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local docs_view = require('cmp.view.docs_view')
|
||||
local custom_entries_view = require('cmp.view.custom_entries_view')
|
||||
local wildmenu_entries_view = require('cmp.view.wildmenu_entries_view')
|
||||
local native_entries_view = require('cmp.view.native_entries_view')
|
||||
local ghost_text_view = require('cmp.view.ghost_text_view')
|
||||
|
||||
@ -12,6 +13,7 @@ local ghost_text_view = require('cmp.view.ghost_text_view')
|
||||
---@field private resolve_dedup cmp.AsyncDedup
|
||||
---@field private native_entries_view cmp.NativeEntriesView
|
||||
---@field private custom_entries_view cmp.CustomEntriesView
|
||||
---@field private wildmenu_entries_view cmp.CustomEntriesView
|
||||
---@field private change_dedup cmp.AsyncDedup
|
||||
---@field private docs_view cmp.DocsView
|
||||
---@field private ghost_text_view cmp.GhostTextView
|
||||
@ -23,6 +25,7 @@ view.new = function()
|
||||
self.resolve_dedup = async.dedup()
|
||||
self.custom_entries_view = custom_entries_view.new()
|
||||
self.native_entries_view = native_entries_view.new()
|
||||
self.wildmenu_entries_view = wildmenu_entries_view.new()
|
||||
self.docs_view = docs_view.new()
|
||||
self.ghost_text_view = ghost_text_view.new()
|
||||
self.event = event.new()
|
||||
@ -47,7 +50,7 @@ end
|
||||
view.open = function(self, ctx, sources)
|
||||
local source_group_map = {}
|
||||
for _, s in ipairs(sources) do
|
||||
local group_index = s:get_config().group_index or 0
|
||||
local group_index = s:get_source_config().group_index or 0
|
||||
if not source_group_map[group_index] then
|
||||
source_group_map[group_index] = {}
|
||||
end
|
||||
@ -77,10 +80,10 @@ view.open = function(self, ctx, sources)
|
||||
-- create filtered entries.
|
||||
local offset = ctx.cursor.col
|
||||
for i, s in ipairs(source_group) do
|
||||
if s.offset <= offset then
|
||||
if s.offset <= ctx.cursor.col then
|
||||
if not has_triggered_by_symbol_source or s.is_triggered_by_symbol then
|
||||
-- source order priority bonus.
|
||||
local priority = s:get_config().priority or ((#source_group - (i - 1)) * config.get().sorting.priority_weight)
|
||||
local priority = s:get_source_config().priority or ((#source_group - (i - 1)) * config.get().sorting.priority_weight)
|
||||
|
||||
for _, e in ipairs(s:get_entries(ctx)) do
|
||||
e.score = e.score + priority
|
||||
@ -109,7 +112,7 @@ view.open = function(self, ctx, sources)
|
||||
end
|
||||
end
|
||||
|
||||
-- close.
|
||||
-- complete_done.
|
||||
if #entries == 0 then
|
||||
self:close()
|
||||
end
|
||||
@ -117,6 +120,11 @@ end
|
||||
|
||||
---Close menu
|
||||
view.close = function(self)
|
||||
if self:visible() then
|
||||
self.event:emit('complete_done', {
|
||||
entry = self:_get_entries_view():get_selected_entry(),
|
||||
})
|
||||
end
|
||||
self:_get_entries_view():close()
|
||||
self.docs_view:close()
|
||||
self.ghost_text_view:hide()
|
||||
@ -153,6 +161,17 @@ view.select_prev_item = function(self, option)
|
||||
self:_get_entries_view():select_prev_item(option)
|
||||
end
|
||||
|
||||
---Get offset.
|
||||
view.get_offset = function(self)
|
||||
return self:_get_entries_view():get_offset()
|
||||
end
|
||||
|
||||
---Get entries.
|
||||
---@return cmp.Entry[]
|
||||
view.get_entries = function(self)
|
||||
return self:_get_entries_view():get_entries()
|
||||
end
|
||||
|
||||
---Get first entry
|
||||
---@param self cmp.Entry|nil
|
||||
view.get_first_entry = function(self)
|
||||
@ -174,54 +193,51 @@ end
|
||||
---Return current configured entries_view
|
||||
---@return cmp.CustomEntriesView|cmp.NativeEntriesView
|
||||
view._get_entries_view = function(self)
|
||||
local c = config.get()
|
||||
self.native_entries_view.event:clear()
|
||||
self.custom_entries_view.event:clear()
|
||||
self.wildmenu_entries_view.event:clear()
|
||||
|
||||
if c.experimental.native_menu then
|
||||
self.native_entries_view.event:on('change', function()
|
||||
self:on_entry_change()
|
||||
end)
|
||||
return self.native_entries_view
|
||||
else
|
||||
self.custom_entries_view.event:on('change', function()
|
||||
self:on_entry_change()
|
||||
end)
|
||||
return self.custom_entries_view
|
||||
local c = config.get()
|
||||
local v = self.custom_entries_view
|
||||
if (c.view and c.view.entries and (c.view.entries.name or c.view.entries)) == 'wildmenu' then
|
||||
v = self.wildmenu_entries_view
|
||||
elseif (c.view and c.view.entries and (c.view.entries.name or c.view.entries)) == 'native' then
|
||||
v = self.native_entries_view
|
||||
end
|
||||
v.event:on('change', function()
|
||||
self:on_entry_change()
|
||||
end)
|
||||
return v
|
||||
end
|
||||
|
||||
---On entry change
|
||||
view.on_entry_change = async.throttle(
|
||||
vim.schedule_wrap(function(self)
|
||||
if not self:visible() then
|
||||
return
|
||||
view.on_entry_change = async.throttle(function(self)
|
||||
if not self:visible() then
|
||||
return
|
||||
end
|
||||
local e = self:get_selected_entry()
|
||||
if e then
|
||||
for _, c in ipairs(config.get().confirmation.get_commit_characters(e:get_commit_characters())) do
|
||||
keymap.listen('i', c, function(...)
|
||||
self.event:emit('keymap', ...)
|
||||
end)
|
||||
end
|
||||
local e = self:get_selected_entry()
|
||||
if e then
|
||||
for _, c in ipairs(config.get().confirmation.get_commit_characters(e:get_commit_characters())) do
|
||||
keymap.listen('i', c, function(...)
|
||||
self.event:emit('keymap', ...)
|
||||
end)
|
||||
e:resolve(vim.schedule_wrap(self.resolve_dedup(function()
|
||||
if not self:visible() then
|
||||
return
|
||||
end
|
||||
e:resolve(vim.schedule_wrap(self.resolve_dedup(function()
|
||||
if not self:visible() then
|
||||
return
|
||||
end
|
||||
self.docs_view:open(e, self:_get_entries_view():info())
|
||||
end)))
|
||||
else
|
||||
self.docs_view:close()
|
||||
end
|
||||
self.docs_view:open(e, self:_get_entries_view():info())
|
||||
end)))
|
||||
else
|
||||
self.docs_view:close()
|
||||
end
|
||||
|
||||
e = e or self:get_first_entry()
|
||||
if e then
|
||||
self.ghost_text_view:show(e)
|
||||
else
|
||||
self.ghost_text_view:hide()
|
||||
end
|
||||
end),
|
||||
20
|
||||
)
|
||||
e = e or self:get_first_entry()
|
||||
if e then
|
||||
self.ghost_text_view:show(e)
|
||||
else
|
||||
self.ghost_text_view:hide()
|
||||
end
|
||||
end, 20)
|
||||
|
||||
return view
|
||||
|
@ -5,6 +5,7 @@ vim.g.loaded_cmp = true
|
||||
|
||||
local api = require "cmp.utils.api"
|
||||
local misc = require('cmp.utils.misc')
|
||||
local types = require('cmp.types')
|
||||
local config = require('cmp.config')
|
||||
local highlight = require('cmp.utils.highlight')
|
||||
|
||||
@ -20,11 +21,12 @@ vim.cmd [[
|
||||
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 `<C-f>`
|
||||
augroup END
|
||||
]]
|
||||
|
||||
misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'enter' }, function()
|
||||
if config.get().experimental.native_menu then
|
||||
if config.is_native_menu() then
|
||||
return
|
||||
end
|
||||
if vim.fn.expand('<afile>')~= '=' then
|
||||
@ -78,6 +80,11 @@ misc.set(_G, { 'cmp', 'plugin', 'colorscheme' }, function()
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
})
|
||||
for name in pairs(types.lsp.CompletionItemKind) do
|
||||
if type(name) == 'string' then
|
||||
vim.cmd(([[highlight default link CmpItemKind%sDefault CmpItemKind]]):format(name))
|
||||
end
|
||||
end
|
||||
highlight.inherit('CmpItemMenuDefault', 'Pmenu', {
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
@ -86,32 +93,40 @@ end)
|
||||
_G.cmp.plugin.colorscheme()
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbr') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbr CmpItemAbbrDefault]]
|
||||
vim.cmd [[highlight default link CmpItemAbbr CmpItemAbbrDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbrDeprecated') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbrDeprecated CmpItemAbbrDeprecatedDefault]]
|
||||
vim.cmd [[highlight default link CmpItemAbbrDeprecated CmpItemAbbrDeprecatedDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbrMatch') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbrMatch CmpItemAbbrMatchDefault]]
|
||||
vim.cmd [[highlight default link CmpItemAbbrMatch CmpItemAbbrMatchDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbrMatchFuzzy') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbrMatchFuzzy CmpItemAbbrMatchFuzzyDefault]]
|
||||
vim.cmd [[highlight default link CmpItemAbbrMatchFuzzy CmpItemAbbrMatchFuzzyDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemKind') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemKind CmpItemKindDefault]]
|
||||
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]]
|
||||
vim.cmd [[highlight default link CmpItemMenu CmpItemMenuDefault]]
|
||||
end
|
||||
|
||||
vim.cmd [[command! CmpStatus lua require('cmp').status()]]
|
||||
|
||||
vim.cmd [[doautocmd <nomodeline> User cmp#ready]]
|
||||
vim.cmd [[doautocmd <nomodeline> User CmpReady]]
|
||||
|
||||
if vim.on_key then
|
||||
vim.on_key(function(keys)
|
||||
|
Loading…
Reference in New Issue
Block a user