mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-02 22:20:06 +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)
|
- [indent-blankline.nvim](https://github.com/lukas-reineke/indent-blankline.nvim/tree/17a83ea765831cb0cc64f768b8c3f43479b90bbe)
|
||||||
- [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/tree/507f8a570ac2b8b8dabdd0f62da3b3194bf822f8)
|
- [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/tree/507f8a570ac2b8b8dabdd0f62da3b3194bf822f8)
|
||||||
- [deoplete-lsp](https://github.com/deoplete-plugins/deoplete-lsp/tree/6299a22bedfb4f814d95cb0010291501472f8fd0)
|
- [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
|
- name: Setup neovim
|
||||||
uses: rhysd/action-setup-vim@v1
|
uses: rhysd/action-setup-vim@v1
|
||||||
with:
|
with:
|
||||||
|
version: nightly
|
||||||
neovim: true
|
neovim: true
|
||||||
|
|
||||||
- name: Setup lua
|
- name: Setup lua
|
||||||
|
2
bundle/nvim-cmp/.gitignore
vendored
2
bundle/nvim-cmp/.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
|
doc/tags
|
||||||
utils/stylua
|
utils/stylua
|
||||||
|
|
||||||
|
@ -11,25 +11,18 @@ Readme!
|
|||||||
1. nvim-cmp's breaking changes are documented [here](https://github.com/hrsh7th/nvim-cmp/issues/231).
|
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.
|
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.
|
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
|
Concept
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
- Full support for LSP completion related capabilities
|
||||||
|
- Powerful customizability via Lua functions
|
||||||
|
- Smart handling of key mapping
|
||||||
- No flicker
|
- 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
|
Setup
|
||||||
@ -37,9 +30,9 @@ Setup
|
|||||||
|
|
||||||
### Recommended Configuration
|
### 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)
|
call plug#begin(s:plug_dir)
|
||||||
Plug 'neovim/nvim-lspconfig'
|
Plug 'neovim/nvim-lspconfig'
|
||||||
Plug 'hrsh7th/cmp-nvim-lsp'
|
Plug 'hrsh7th/cmp-nvim-lsp'
|
||||||
@ -78,12 +71,12 @@ lua <<EOF
|
|||||||
expand = function(args)
|
expand = function(args)
|
||||||
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
|
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
|
||||||
-- require('luasnip').lsp_expand(args.body) -- For `luasnip` 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.
|
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
|
||||||
-- require'snippy'.expand_snippet(args.body) -- For `snippy` users.
|
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
mapping = {
|
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-f>'] = cmp.mapping(cmp.mapping.scroll_docs(4), { 'i', 'c' }),
|
||||||
['<C-Space>'] = cmp.mapping(cmp.mapping.complete(), { '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.
|
['<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(),
|
i = cmp.mapping.abort(),
|
||||||
c = cmp.mapping.close(),
|
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({
|
sources = cmp.config.sources({
|
||||||
{ name = 'nvim_lsp' },
|
{ 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).
|
-- Use buffer source for `/` (if you enabled `native_menu`, this won't work anymore).
|
||||||
cmp.setup.cmdline('/', {
|
cmp.setup.cmdline('/', {
|
||||||
sources = {
|
sources = {
|
||||||
@ -131,646 +133,83 @@ EOF
|
|||||||
|
|
||||||
### Where can I find more completion sources?
|
### 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).
|
nvim-cmp can be used as flexible omnifunc manager.
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
mapping = {
|
local cmp = require('cmp')
|
||||||
['<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')
|
|
||||||
cmp.setup {
|
cmp.setup {
|
||||||
formatting = {
|
completion = {
|
||||||
format = lspkind.cmp_format(),
|
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.
|
_G.vimrc = _G.vimrc or {}
|
||||||
|
_G.vimrc.cmp = _G.vimrc.cmp or {}
|
||||||
#### experimental.native_menu (type: boolean)
|
_G.vimrc.cmp.lsp = function()
|
||||||
|
cmp.complete({
|
||||||
Use vim's native completion menu instead of custom floating menu.
|
config = {
|
||||||
|
sources = {
|
||||||
Default: `false`
|
{ name = 'nvim_lsp' }
|
||||||
|
}
|
||||||
#### 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
|
|
||||||
end
|
end
|
||||||
|
_G.vimrc.cmp.snippet = function()
|
||||||
---Return the source is available or not.
|
cmp.complete({
|
||||||
---@return boolean
|
config = {
|
||||||
function source:is_available()
|
sources = {
|
||||||
return true
|
{ name = 'vsnip' }
|
||||||
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' },
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---Resolve completion item that will be called when the item selected or before the item confirmation.
|
vim.cmd([[
|
||||||
---@param completion_item lsp.CompletionItem
|
inoremap <C-x><C-o> <Cmd>lua vimrc.cmp.lsp()<CR>
|
||||||
---@param callback fun(completion_item: lsp.CompletionItem|nil)
|
inoremap <C-x><C-s> <Cmd>lua vimrc.cmp.snippet()<CR>
|
||||||
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())
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
cmp.setup {
|
||||||
let s:source = {}
|
completion = {
|
||||||
|
autocomplete = false, -- disable auto-completion.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function! s:source.new() abort
|
_G.vimrc = _G.vimrc or {}
|
||||||
return extend(deepcopy(s:source))
|
_G.vimrc.cmp = _G.vimrc.cmp or {}
|
||||||
endfunction
|
_G.vimrc.cmp.on_text_changed = function()
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||||
" The other APIs are also available.
|
local line = vim.api.nvim_get_current_line()
|
||||||
|
local before = string.sub(line, 1, cursor[2] + 1)
|
||||||
function! s:source.complete(params, callback) abort
|
if before:match('%s*$') then
|
||||||
call a:callback({
|
cmp.complete() -- Trigger completion only if the cursor is placed at the end of line.
|
||||||
\ { 'label': 'January' },
|
end
|
||||||
\ { 'label': 'February' },
|
end
|
||||||
\ { 'label': 'March' },
|
vim.cmd([[
|
||||||
\ { 'label': 'April' },
|
augroup vimrc
|
||||||
\ { 'label': 'May' },
|
autocmd
|
||||||
\ { 'label': 'June' },
|
autocmd TextChanged,TextChangedI,TextChangedP * call luaeval('vimrc.cmp.on_text_changed()')
|
||||||
\ { 'label': 'July' },
|
augroup END
|
||||||
\ { 'label': 'August' },
|
]])
|
||||||
\ { 'label': 'September' },
|
|
||||||
\ { 'label': 'October' },
|
|
||||||
\ { 'label': 'November' },
|
|
||||||
\ { 'label': 'December' },
|
|
||||||
\ })
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
call cmp#register_source('month', s:source.new())
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,18 +1,6 @@
|
|||||||
let s:bridge_id = 0
|
let s:bridge_id = 0
|
||||||
let s:sources = {}
|
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
|
" 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>
|
---@type table<number, cmp.ConfigSchema>
|
||||||
config.buffers = {}
|
config.buffers = {}
|
||||||
|
|
||||||
|
---@type table<string, cmp.ConfigSchema>
|
||||||
|
config.filetypes = {}
|
||||||
|
|
||||||
---@type table<string, cmp.ConfigSchema>
|
---@type table<string, cmp.ConfigSchema>
|
||||||
config.cmdline = {}
|
config.cmdline = {}
|
||||||
|
|
||||||
|
---@type cmp.ConfigSchema
|
||||||
|
config.onetime = {}
|
||||||
|
|
||||||
---Set configuration for global.
|
---Set configuration for global.
|
||||||
---@param c cmp.ConfigSchema
|
---@param c cmp.ConfigSchema
|
||||||
config.set_global = function(c)
|
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 or 1
|
||||||
config.global.revision = config.global.revision + 1
|
config.global.revision = config.global.revision + 1
|
||||||
end
|
end
|
||||||
@ -33,31 +39,82 @@ end
|
|||||||
---@param bufnr number|nil
|
---@param bufnr number|nil
|
||||||
config.set_buffer = function(c, bufnr)
|
config.set_buffer = function(c, bufnr)
|
||||||
local revision = (config.buffers[bufnr] or {}).revision or 1
|
local revision = (config.buffers[bufnr] or {}).revision or 1
|
||||||
config.buffers[bufnr] = c
|
config.buffers[bufnr] = c or {}
|
||||||
config.buffers[bufnr].revision = revision + 1
|
config.buffers[bufnr].revision = revision + 1
|
||||||
end
|
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
|
---Set configuration for cmdline
|
||||||
config.set_cmdline = function(c, type)
|
---@param c cmp.ConfigSchema
|
||||||
local revision = (config.cmdline[type] or {}).revision or 1
|
---@param cmdtype string
|
||||||
config.cmdline[type] = c
|
config.set_cmdline = function(c, cmdtype)
|
||||||
config.cmdline[type].revision = revision + 1
|
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
|
end
|
||||||
|
|
||||||
---@return cmp.ConfigSchema
|
---@return cmp.ConfigSchema
|
||||||
config.get = function()
|
config.get = function()
|
||||||
local global = config.global
|
local global_config = config.global
|
||||||
if api.is_cmdline_mode() then
|
if config.onetime.sources then
|
||||||
local type = vim.fn.getcmdtype()
|
local onetime_config = config.onetime
|
||||||
local cmdline = config.cmdline[type] or { revision = 1, sources = {} }
|
return config.cache:ensure({
|
||||||
return config.cache:ensure({ 'get_cmdline', type, global.revision or 0, cmdline.revision or 0 }, function()
|
'get',
|
||||||
return misc.merge(config.normalize(cmdline), config.normalize(global))
|
'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)
|
end)
|
||||||
else
|
else
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local buffer = config.buffers[bufnr] or { revision = 1 }
|
local filetype = vim.api.nvim_buf_get_option(bufnr, 'filetype')
|
||||||
return config.cache:ensure({ 'get_buffer', bufnr, global.revision or 0, buffer.revision or 0 }, function()
|
local buffer_config = config.buffers[bufnr] or { revision = 1 }
|
||||||
return misc.merge(config.normalize(buffer), config.normalize(global))
|
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
|
end
|
||||||
end
|
end
|
||||||
@ -78,19 +135,31 @@ config.get_source_config = function(name)
|
|||||||
local c = config.get()
|
local c = config.get()
|
||||||
for _, s in ipairs(c.sources) do
|
for _, s in ipairs(c.sources) do
|
||||||
if s.name == name then
|
if s.name == name then
|
||||||
if type(s.opts) ~= 'table' then
|
|
||||||
s.opts = {}
|
|
||||||
end
|
|
||||||
return s
|
return s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
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
|
---Normalize mapping key
|
||||||
---@param c cmp.ConfigSchema
|
---@param c cmp.ConfigSchema
|
||||||
---@return cmp.ConfigSchema
|
---@return cmp.ConfigSchema
|
||||||
config.normalize = function(c)
|
config.normalize = function(c)
|
||||||
|
-- make sure c is not 'nil'
|
||||||
|
c = c == nil and {} or c
|
||||||
|
|
||||||
if c.mapping then
|
if c.mapping then
|
||||||
local normalized = {}
|
local normalized = {}
|
||||||
for k, v in pairs(c.mapping) do
|
for k, v in pairs(c.mapping) do
|
||||||
@ -98,6 +167,39 @@ config.normalize = function(c)
|
|||||||
end
|
end
|
||||||
c.mapping = normalized
|
c.mapping = normalized
|
||||||
end
|
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
|
return c
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
local types = require('cmp.types')
|
local types = require('cmp.types')
|
||||||
|
local cache = require('cmp.utils.cache')
|
||||||
local misc = require('cmp.utils.misc')
|
local misc = require('cmp.utils.misc')
|
||||||
|
|
||||||
local compare = {}
|
local compare = {}
|
||||||
@ -100,4 +101,135 @@ compare.order = function(entry1, entry2)
|
|||||||
end
|
end
|
||||||
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
|
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 compare = require('cmp.config.compare')
|
||||||
local mapping = require('cmp.config.mapping')
|
local mapping = require('cmp.config.mapping')
|
||||||
|
local keymap = require('cmp.utils.keymap')
|
||||||
local types = require('cmp.types')
|
local types = require('cmp.types')
|
||||||
|
|
||||||
local WIDE_HEIGHT = 40
|
local WIDE_HEIGHT = 40
|
||||||
@ -8,58 +9,15 @@ local WIDE_HEIGHT = 40
|
|||||||
return function()
|
return function()
|
||||||
return {
|
return {
|
||||||
enabled = function()
|
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,
|
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,
|
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 = {
|
mapping = {
|
||||||
['<Down>'] = mapping({
|
['<Down>'] = mapping({
|
||||||
i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }),
|
i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }),
|
||||||
@ -80,30 +38,38 @@ return function()
|
|||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
['<Tab>'] = mapping({
|
['<Tab>'] = mapping({
|
||||||
c = function(fallback)
|
c = function()
|
||||||
local cmp = require('cmp')
|
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
|
if cmp.visible() then
|
||||||
cmp.select_next_item()
|
cmp.select_next_item()
|
||||||
else
|
else
|
||||||
cmp.complete()
|
cmp.complete()
|
||||||
end
|
end
|
||||||
else
|
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
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
['<S-Tab>'] = mapping({
|
['<S-Tab>'] = mapping({
|
||||||
c = function(fallback)
|
c = function()
|
||||||
local cmp = require('cmp')
|
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
|
if cmp.visible() then
|
||||||
cmp.select_prev_item()
|
cmp.select_prev_item()
|
||||||
else
|
else
|
||||||
cmp.complete()
|
cmp.complete()
|
||||||
end
|
end
|
||||||
else
|
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
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
@ -113,6 +79,21 @@ return function()
|
|||||||
['<C-e>'] = mapping.abort(),
|
['<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 = {
|
formatting = {
|
||||||
fields = { 'abbr', 'kind', 'menu' },
|
fields = { 'abbr', 'kind', 'menu' },
|
||||||
format = function(_, vim_item)
|
format = function(_, vim_item)
|
||||||
@ -120,11 +101,52 @@ return function()
|
|||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
||||||
experimental = {
|
matching = {
|
||||||
native_menu = false,
|
disallow_fuzzy_matching = false,
|
||||||
ghost_text = 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 = {},
|
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
|
end
|
||||||
|
@ -13,9 +13,19 @@ mapping = setmetatable({}, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
---Invoke completion
|
---Invoke completion
|
||||||
mapping.complete = function()
|
---@param option cmp.CompleteParams
|
||||||
|
mapping.complete = function(option)
|
||||||
return function(fallback)
|
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()
|
fallback()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
local debug = require('cmp.utils.debug')
|
local debug = require('cmp.utils.debug')
|
||||||
|
local str = require('cmp.utils.str')
|
||||||
local char = require('cmp.utils.char')
|
local char = require('cmp.utils.char')
|
||||||
local pattern = require('cmp.utils.pattern')
|
local pattern = require('cmp.utils.pattern')
|
||||||
local feedkeys = require('cmp.utils.feedkeys')
|
local feedkeys = require('cmp.utils.feedkeys')
|
||||||
@ -14,14 +15,13 @@ local api = require('cmp.utils.api')
|
|||||||
local event = require('cmp.utils.event')
|
local event = require('cmp.utils.event')
|
||||||
|
|
||||||
local SOURCE_TIMEOUT = 500
|
local SOURCE_TIMEOUT = 500
|
||||||
local THROTTLE_TIME = 120
|
local DEBOUNCE_TIME = 80
|
||||||
local DEBOUNCE_TIME = 20
|
local THROTTLE_TIME = 40
|
||||||
|
|
||||||
---@class cmp.Core
|
---@class cmp.Core
|
||||||
---@field public suspending boolean
|
---@field public suspending boolean
|
||||||
---@field public view cmp.View
|
---@field public view cmp.View
|
||||||
---@field public sources cmp.Source[]
|
---@field public sources cmp.Source[]
|
||||||
---@field public sources_by_name table<string, cmp.Source>
|
|
||||||
---@field public context cmp.Context
|
---@field public context cmp.Context
|
||||||
---@field public event cmp.Event
|
---@field public event cmp.Event
|
||||||
local core = {}
|
local core = {}
|
||||||
@ -30,13 +30,15 @@ core.new = function()
|
|||||||
local self = setmetatable({}, { __index = core })
|
local self = setmetatable({}, { __index = core })
|
||||||
self.suspending = false
|
self.suspending = false
|
||||||
self.sources = {}
|
self.sources = {}
|
||||||
self.sources_by_name = {}
|
|
||||||
self.context = context.new()
|
self.context = context.new()
|
||||||
self.event = event.new()
|
self.event = event.new()
|
||||||
self.view = view.new()
|
self.view = view.new()
|
||||||
self.view.event:on('keymap', function(...)
|
self.view.event:on('keymap', function(...)
|
||||||
self:on_keymap(...)
|
self:on_keymap(...)
|
||||||
end)
|
end)
|
||||||
|
self.view.event:on('complete_done', function(evt)
|
||||||
|
self.event:emit('complete_done', evt)
|
||||||
|
end)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -44,19 +46,11 @@ end
|
|||||||
---@param s cmp.Source
|
---@param s cmp.Source
|
||||||
core.register_source = function(self, s)
|
core.register_source = function(self, s)
|
||||||
self.sources[s.id] = 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
|
end
|
||||||
|
|
||||||
---Unregister source
|
---Unregister source
|
||||||
---@param source_id string
|
---@param source_id string
|
||||||
core.unregister_source = function(self, source_id)
|
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
|
self.sources[source_id] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -86,14 +80,23 @@ core.suspend = function(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Get sources that sorted by priority
|
---Get sources that sorted by priority
|
||||||
---@param statuses cmp.SourceStatus[]
|
---@param filter cmp.SourceStatus[]|fun(s: cmp.Source): boolean
|
||||||
---@return cmp.Source[]
|
---@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 = {}
|
local sources = {}
|
||||||
for _, c in pairs(config.get().sources) do
|
for _, c in pairs(config.get().sources) do
|
||||||
for _, s in ipairs(self.sources_by_name[c.name] or {}) do
|
for _, s in pairs(self.sources) do
|
||||||
if not statuses or vim.tbl_contains(statuses, s.status) then
|
if c.name == s.name then
|
||||||
if s:is_available() then
|
if s:is_available() and f(s) then
|
||||||
table.insert(sources, s)
|
table.insert(sources, s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -118,6 +121,7 @@ core.on_keymap = function(self, keys, fallback)
|
|||||||
local is_printable = char.is_printable(string.byte(chars, 1))
|
local is_printable = char.is_printable(string.byte(chars, 1))
|
||||||
self:confirm(e, {
|
self:confirm(e, {
|
||||||
behavior = is_printable and 'insert' or 'replace',
|
behavior = is_printable and 'insert' or 'replace',
|
||||||
|
commit_character = chars,
|
||||||
}, function()
|
}, function()
|
||||||
local ctx = self:get_context()
|
local ctx = self:get_context()
|
||||||
local word = e:get_word()
|
local word = e:get_word()
|
||||||
@ -154,7 +158,6 @@ core.on_change = function(self, trigger_event)
|
|||||||
self:get_context({ reason = types.cmp.ContextReason.Auto })
|
self:get_context({ reason = types.cmp.ContextReason.Auto })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self:autoindent(trigger_event, function()
|
self:autoindent(trigger_event, function()
|
||||||
local ctx = self:get_context({ reason = types.cmp.ContextReason.Auto })
|
local ctx = self:get_context({ reason = types.cmp.ContextReason.Auto })
|
||||||
debug.log(('ctx: `%s`'):format(ctx.cursor_before_line))
|
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
|
if vim.tbl_contains(config.get().completion.autocomplete or {}, trigger_event) then
|
||||||
self:complete(ctx)
|
self:complete(ctx)
|
||||||
else
|
else
|
||||||
self.filter.timeout = THROTTLE_TIME
|
self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
|
||||||
self:filter()
|
self:filter()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -204,92 +207,140 @@ core.autoindent = function(self, trigger_event, callback)
|
|||||||
return callback()
|
return callback()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Scan indentkeys.
|
-- Reset current completion if indentkeys matched.
|
||||||
for _, key in ipairs(vim.split(vim.bo.indentkeys, ',')) do
|
for _, key in ipairs(vim.split(vim.bo.indentkeys, ',')) do
|
||||||
if vim.tbl_contains({ '=' .. prefix, '0=' .. prefix }, key) then
|
if vim.tbl_contains({ '=' .. prefix, '0=' .. prefix }, key) then
|
||||||
local release = self:suspend()
|
self:reset()
|
||||||
vim.schedule(function() -- Check autoindent already applied.
|
self:set_context(context.empty())
|
||||||
if cursor_before_line == api.get_cursor_before_line() then
|
break
|
||||||
feedkeys.call(keymap.autoindent(), 'n', function()
|
|
||||||
release()
|
|
||||||
callback()
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
callback()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- indentkeys does not matched.
|
|
||||||
callback()
|
callback()
|
||||||
end
|
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
|
---Invoke completion
|
||||||
---@param ctx cmp.Context
|
---@param ctx cmp.Context
|
||||||
core.complete = function(self, ctx)
|
core.complete = function(self, ctx)
|
||||||
if not api.is_suitable_mode() then
|
if not api.is_suitable_mode() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self:set_context(ctx)
|
self:set_context(ctx)
|
||||||
|
|
||||||
for _, s in ipairs(self:get_sources({ source.SourceStatus.WAITING, source.SourceStatus.COMPLETED })) do
|
-- Invoke completion sources.
|
||||||
s:complete(
|
local sources = self:get_sources()
|
||||||
ctx,
|
for _, s in ipairs(sources) do
|
||||||
(function(src)
|
local callback
|
||||||
local callback
|
callback = (function(s_)
|
||||||
callback = function()
|
return function()
|
||||||
local new = context.new(ctx)
|
local new = context.new(ctx)
|
||||||
if new:changed(new.prev_context) and ctx == self.context then
|
if s_.incomplete and new:changed(s_.context) then
|
||||||
src:complete(new, callback)
|
s_:complete(new, callback)
|
||||||
else
|
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.stop()
|
||||||
self.filter.timeout = DEBOUNCE_TIME
|
self.filter.timeout = self.view:visible() and DEBOUNCE_TIME or 0
|
||||||
self:filter()
|
self:filter()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return callback
|
end
|
||||||
end)(s)
|
end)(s)
|
||||||
)
|
s:complete(ctx, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.filter.timeout = THROTTLE_TIME
|
if not self.view:get_active_entry() then
|
||||||
self:filter()
|
self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
|
||||||
|
self:filter()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Update completion menu
|
---Update completion menu
|
||||||
core.filter = async.throttle(
|
core.filter = async.throttle(function(self)
|
||||||
vim.schedule_wrap(function(self)
|
self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0
|
||||||
if not api.is_suitable_mode() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if self.view:get_active_entry() ~= nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local ctx = self:get_context()
|
|
||||||
|
|
||||||
-- To wait for processing source for that's timeout.
|
-- Check invalid condition.
|
||||||
local sources = {}
|
local ignore = false
|
||||||
for _, s in ipairs(self:get_sources({ source.SourceStatus.FETCHING, source.SourceStatus.COMPLETED })) do
|
ignore = ignore or not api.is_suitable_mode()
|
||||||
local time = SOURCE_TIMEOUT - s:get_fetching_time()
|
if ignore then
|
||||||
if not s.incomplete and time > 0 then
|
return
|
||||||
if #sources == 0 then
|
end
|
||||||
self.filter.stop()
|
|
||||||
self.filter.timeout = time + 1
|
|
||||||
self:filter()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
table.insert(sources, s)
|
|
||||||
end
|
|
||||||
self.filter.timeout = THROTTLE_TIME
|
|
||||||
|
|
||||||
self.view:open(ctx, sources)
|
-- Check fetching sources.
|
||||||
end),
|
local sources = {}
|
||||||
THROTTLE_TIME
|
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.
|
---Confirm completion.
|
||||||
---@param e cmp.Entry
|
---@param e cmp.Entry
|
||||||
@ -308,10 +359,11 @@ core.confirm = function(self, e, option, callback)
|
|||||||
-- Close menus.
|
-- Close menus.
|
||||||
self.view:close()
|
self.view:close()
|
||||||
|
|
||||||
|
feedkeys.call(keymap.indentkeys(), 'n')
|
||||||
feedkeys.call('', 'n', function()
|
feedkeys.call('', 'n', function()
|
||||||
local ctx = context.new()
|
local ctx = context.new()
|
||||||
local keys = {}
|
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, e:get_word())
|
||||||
table.insert(keys, keymap.undobreak())
|
table.insert(keys, keymap.undobreak())
|
||||||
feedkeys.call(table.concat(keys, ''), 'int')
|
feedkeys.call(table.concat(keys, ''), 'int')
|
||||||
@ -320,9 +372,9 @@ core.confirm = function(self, e, option, callback)
|
|||||||
local ctx = context.new()
|
local ctx = context.new()
|
||||||
if api.is_cmdline_mode() then
|
if api.is_cmdline_mode() then
|
||||||
local keys = {}
|
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()))
|
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
|
else
|
||||||
vim.api.nvim_buf_set_text(0, ctx.cursor.row - 1, e:get_offset() - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, {
|
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()),
|
string.sub(e.context.cursor_before_line, e:get_offset()),
|
||||||
@ -331,8 +383,8 @@ core.confirm = function(self, e, option, callback)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
feedkeys.call('', 'n', function()
|
feedkeys.call('', 'n', function()
|
||||||
|
local ctx = context.new()
|
||||||
if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then
|
if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then
|
||||||
local pre = context.new()
|
|
||||||
e:resolve(function()
|
e:resolve(function()
|
||||||
local new = context.new()
|
local new = context.new()
|
||||||
local text_edits = misc.safe(e:get_completion_item().additionalTextEdits) or {}
|
local text_edits = misc.safe(e:get_completion_item().additionalTextEdits) or {}
|
||||||
@ -341,8 +393,8 @@ core.confirm = function(self, e, option, callback)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local has_cursor_line_text_edit = (function()
|
local has_cursor_line_text_edit = (function()
|
||||||
local minrow = math.min(pre.cursor.row, new.cursor.row)
|
local minrow = math.min(ctx.cursor.row, new.cursor.row)
|
||||||
local maxrow = math.max(pre.cursor.row, new.cursor.row)
|
local maxrow = math.max(ctx.cursor.row, new.cursor.row)
|
||||||
for _, te in ipairs(text_edits) do
|
for _, te in ipairs(text_edits) do
|
||||||
local srow = te.range.start.line + 1
|
local srow = te.range.start.line + 1
|
||||||
local erow = te.range['end'].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
|
if has_cursor_line_text_edit then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
vim.fn['cmp#apply_text_edits'](new.bufnr, text_edits)
|
vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, 'utf-16')
|
||||||
end)
|
end)
|
||||||
else
|
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
|
||||||
end)
|
end)
|
||||||
feedkeys.call('', 'n', function()
|
feedkeys.call('', 'n', function()
|
||||||
@ -375,8 +427,8 @@ core.confirm = function(self, e, option, callback)
|
|||||||
completion_item.textEdit.range = e:get_insert_range()
|
completion_item.textEdit.range = e:get_insert_range()
|
||||||
end
|
end
|
||||||
|
|
||||||
local diff_before = e.context.cursor.character - completion_item.textEdit.range.start.character
|
local diff_before = math.max(0, 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_after = math.max(0, completion_item.textEdit.range['end'].character - e.context.cursor.character)
|
||||||
local new_text = completion_item.textEdit.newText
|
local new_text = completion_item.textEdit.newText
|
||||||
|
|
||||||
if api.is_insert_mode() then
|
if api.is_insert_mode() then
|
||||||
@ -388,14 +440,14 @@ core.confirm = function(self, e, option, callback)
|
|||||||
if is_snippet then
|
if is_snippet then
|
||||||
completion_item.textEdit.newText = ''
|
completion_item.textEdit.newText = ''
|
||||||
end
|
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 texts = vim.split(completion_item.textEdit.newText, '\n')
|
||||||
local position = completion_item.textEdit.range.start
|
local position = completion_item.textEdit.range.start
|
||||||
position.line = position.line + (#texts - 1)
|
position.line = position.line + (#texts - 1)
|
||||||
if #texts == 1 then
|
if #texts == 1 then
|
||||||
position.character = position.character + vim.str_utfindex(texts[1])
|
position.character = position.character + misc.to_utfindex(texts[1])
|
||||||
else
|
else
|
||||||
position.character = vim.str_utfindex(texts[#texts])
|
position.character = misc.to_utfindex(texts[#texts])
|
||||||
end
|
end
|
||||||
local pos = types.lsp.Position.to_vim(0, position)
|
local pos = types.lsp.Position.to_vim(0, position)
|
||||||
vim.api.nvim_win_set_cursor(0, { pos.row, pos.col - 1 })
|
vim.api.nvim_win_set_cursor(0, { 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('<BS>'), diff_before))
|
||||||
table.insert(keys, string.rep(keymap.t('<Del>'), diff_after))
|
table.insert(keys, string.rep(keymap.t('<Del>'), diff_after))
|
||||||
table.insert(keys, new_text)
|
table.insert(keys, new_text)
|
||||||
feedkeys.call(table.concat(keys, ''), 'int')
|
feedkeys.call(table.concat(keys, ''), 'in')
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
feedkeys.call(keymap.indentkeys(vim.bo.indentkeys), 'n')
|
||||||
feedkeys.call('', 'n', function()
|
feedkeys.call('', 'n', function()
|
||||||
e:execute(vim.schedule_wrap(function()
|
e:execute(vim.schedule_wrap(function()
|
||||||
release()
|
release()
|
||||||
self.event:emit('confirm_done', e)
|
self.event:emit('confirm_done', {
|
||||||
|
entry = e,
|
||||||
|
commit_character = option.commit_character,
|
||||||
|
})
|
||||||
if callback then
|
if callback then
|
||||||
callback()
|
callback()
|
||||||
end
|
end
|
||||||
@ -429,7 +485,7 @@ core.reset = function(self)
|
|||||||
for _, s in pairs(self.sources) do
|
for _, s in pairs(self.sources) do
|
||||||
s:reset()
|
s:reset()
|
||||||
end
|
end
|
||||||
self:get_context() -- To prevent new event
|
self.context = context.empty()
|
||||||
end
|
end
|
||||||
|
|
||||||
return core
|
return core
|
||||||
|
@ -54,10 +54,10 @@ end
|
|||||||
---Make offset value
|
---Make offset value
|
||||||
---@return number
|
---@return number
|
||||||
entry.get_offset = function(self)
|
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
|
local offset = self.source_offset
|
||||||
if misc.safe(self.completion_item.textEdit) then
|
if misc.safe(self:get_completion_item().textEdit) then
|
||||||
local range = misc.safe(self.completion_item.textEdit.insert) or misc.safe(self.completion_item.textEdit.range)
|
local range = misc.safe(self:get_completion_item().textEdit.insert) or misc.safe(self:get_completion_item().textEdit.range)
|
||||||
if range then
|
if range then
|
||||||
local c = misc.to_vimindex(self.context.cursor_line, range.start.character)
|
local c = misc.to_vimindex(self.context.cursor_line, range.start.character)
|
||||||
for idx = c, self.source_offset do
|
for idx = c, self.source_offset do
|
||||||
@ -98,28 +98,32 @@ entry.get_offset = function(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Create word for vim.CompletedItem
|
---Create word for vim.CompletedItem
|
||||||
|
---NOTE: This method doesn't clear the cache after completionItem/resolve.
|
||||||
---@return string
|
---@return string
|
||||||
entry.get_word = function(self)
|
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.
|
--NOTE: This is nvim-cmp specific implementation.
|
||||||
if misc.safe(self.completion_item.word) then
|
if misc.safe(self:get_completion_item().word) then
|
||||||
return self.completion_item.word
|
return self:get_completion_item().word
|
||||||
end
|
end
|
||||||
|
|
||||||
local word
|
local word
|
||||||
if misc.safe(self.completion_item.textEdit) then
|
if misc.safe(self:get_completion_item().textEdit) and not misc.empty(self:get_completion_item().textEdit.newText) then
|
||||||
word = str.trim(self.completion_item.textEdit.newText)
|
word = str.trim(self:get_completion_item().textEdit.newText)
|
||||||
local overwrite = self:get_overwrite()
|
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
if 0 < overwrite[2] or self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
word = vim.lsp.util.parse_snippet(word)
|
||||||
word = str.get_word(word, string.byte(self.context.cursor_after_line, 1))
|
|
||||||
end
|
end
|
||||||
elseif misc.safe(self.completion_item.insertText) then
|
local overwrite = self:get_overwrite()
|
||||||
word = str.trim(self.completion_item.insertText)
|
if 0 < overwrite[2] or self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
if self.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)
|
||||||
word = str.get_word(word)
|
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
|
end
|
||||||
else
|
else
|
||||||
word = str.trim(self.completion_item.label)
|
word = str.trim(self:get_completion_item().label)
|
||||||
end
|
end
|
||||||
return str.oneline(word)
|
return str.oneline(word)
|
||||||
end)
|
end)
|
||||||
@ -128,9 +132,9 @@ end
|
|||||||
---Get overwrite information
|
---Get overwrite information
|
||||||
---@return number, number
|
---@return number, number
|
||||||
entry.get_overwrite = function(self)
|
entry.get_overwrite = function(self)
|
||||||
return self.cache:ensure('get_overwrite', function()
|
return self.cache:ensure({ 'get_overwrite', self.resolved_completion_item and 1 or 0 }, function()
|
||||||
if misc.safe(self.completion_item.textEdit) then
|
if misc.safe(self:get_completion_item().textEdit) then
|
||||||
local r = misc.safe(self.completion_item.textEdit.insert) or misc.safe(self.completion_item.textEdit.range)
|
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 s = misc.to_vimindex(self.context.cursor_line, r.start.character)
|
||||||
local e = misc.to_vimindex(self.context.cursor_line, r['end'].character)
|
local e = misc.to_vimindex(self.context.cursor_line, r['end'].character)
|
||||||
local before = self.context.cursor.col - s
|
local before = self.context.cursor.col - s
|
||||||
@ -144,27 +148,13 @@ end
|
|||||||
---Create filter text
|
---Create filter text
|
||||||
---@return string
|
---@return string
|
||||||
entry.get_filter_text = function(self)
|
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
|
local word
|
||||||
if misc.safe(self.completion_item.filterText) then
|
if misc.safe(self:get_completion_item().filterText) then
|
||||||
word = self.completion_item.filterText
|
word = self:get_completion_item().filterText
|
||||||
else
|
else
|
||||||
word = str.trim(self.completion_item.label)
|
word = str.trim(self:get_completion_item().label)
|
||||||
end
|
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
|
return word
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@ -172,20 +162,20 @@ end
|
|||||||
---Get LSP's insert text
|
---Get LSP's insert text
|
||||||
---@return string
|
---@return string
|
||||||
entry.get_insert_text = function(self)
|
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
|
local word
|
||||||
if misc.safe(self.completion_item.textEdit) then
|
if misc.safe(self:get_completion_item().textEdit) then
|
||||||
word = str.trim(self.completion_item.textEdit.newText)
|
word = str.trim(self:get_completion_item().textEdit.newText)
|
||||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
||||||
end
|
end
|
||||||
elseif misc.safe(self.completion_item.insertText) then
|
elseif misc.safe(self:get_completion_item().insertText) then
|
||||||
word = str.trim(self.completion_item.insertText)
|
word = str.trim(self:get_completion_item().insertText)
|
||||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
word = str.trim(self.completion_item.label)
|
word = str.trim(self:get_completion_item().label)
|
||||||
end
|
end
|
||||||
return word
|
return word
|
||||||
end)
|
end)
|
||||||
@ -194,31 +184,38 @@ end
|
|||||||
---Return the item is deprecated or not.
|
---Return the item is deprecated or not.
|
||||||
---@return boolean
|
---@return boolean
|
||||||
entry.is_deprecated = function(self)
|
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
|
end
|
||||||
|
|
||||||
---Return view information.
|
---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 } }
|
---@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)
|
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 = {}
|
local view = {}
|
||||||
view.abbr = {}
|
-- The result of vim.fn.strdisplaywidth depends on which buffer it was
|
||||||
view.abbr.text = item.abbr or ''
|
-- called in because it reads the values of the option 'tabstop' when
|
||||||
view.abbr.bytes = #view.abbr.text
|
-- rendering <Tab> characters.
|
||||||
view.abbr.width = vim.str_utfindex(view.abbr.text)
|
vim.api.nvim_buf_call(entries_buf, function()
|
||||||
view.abbr.hl_group = self:is_deprecated() and 'CmpItemAbbrDeprecated' or 'CmpItemAbbr'
|
view.abbr = {}
|
||||||
view.kind = {}
|
view.abbr.text = item.abbr or ''
|
||||||
view.kind.text = item.kind or ''
|
view.abbr.bytes = #view.abbr.text
|
||||||
view.kind.bytes = #view.kind.text
|
view.abbr.width = vim.fn.strdisplaywidth(view.abbr.text)
|
||||||
view.kind.width = vim.str_utfindex(view.kind.text)
|
view.abbr.hl_group = item.abbr_hl_group or (self:is_deprecated() and 'CmpItemAbbrDeprecated' or 'CmpItemAbbr')
|
||||||
view.kind.hl_group = 'CmpItemKind'
|
view.kind = {}
|
||||||
view.menu = {}
|
view.kind.text = item.kind or ''
|
||||||
view.menu.text = item.menu or ''
|
view.kind.bytes = #view.kind.text
|
||||||
view.menu.bytes = #view.menu.text
|
view.kind.width = vim.fn.strdisplaywidth(view.kind.text)
|
||||||
view.menu.width = vim.str_utfindex(view.menu.text)
|
view.kind.hl_group = item.kind_hl_group or ('CmpItemKind' .. (types.lsp.CompletionItemKind[self:get_kind()] or ''))
|
||||||
view.menu.hl_group = 'CmpItemMenu'
|
view.menu = {}
|
||||||
view.dup = item.dup
|
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
|
return view
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@ -233,13 +230,16 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
local abbr = str.oneline(completion_item.label)
|
local abbr = str.oneline(completion_item.label)
|
||||||
|
|
||||||
-- ~ indicator
|
-- ~ indicator
|
||||||
|
local is_snippet = false
|
||||||
if #(misc.safe(completion_item.additionalTextEdits) or {}) > 0 then
|
if #(misc.safe(completion_item.additionalTextEdits) or {}) > 0 then
|
||||||
abbr = abbr .. '~'
|
is_snippet = true
|
||||||
elseif completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
elseif completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
local insert_text = self:get_insert_text()
|
is_snippet = self:get_insert_text() ~= word
|
||||||
if word ~= insert_text then
|
elseif completion_item.kind == types.lsp.CompletionItemKind.Snippet then
|
||||||
abbr = abbr .. '~'
|
is_snippet = true
|
||||||
end
|
end
|
||||||
|
if is_snippet then
|
||||||
|
abbr = abbr .. '~'
|
||||||
end
|
end
|
||||||
|
|
||||||
-- append delta text
|
-- append delta text
|
||||||
@ -260,10 +260,12 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- remove duplicated string.
|
-- remove duplicated string.
|
||||||
for i = 1, #word - 1 do
|
if self:get_offset() ~= self.context.cursor.col then
|
||||||
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
|
for i = 1, #word - 1 do
|
||||||
word = string.sub(word, 1, i - 1)
|
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
|
||||||
break
|
word = string.sub(word, 1, i - 1)
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -272,7 +274,7 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
abbr = abbr,
|
abbr = abbr,
|
||||||
kind = types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
|
kind = types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
|
||||||
menu = menu,
|
menu = menu,
|
||||||
dup = self.completion_item.dup or 1,
|
dup = self:get_completion_item().dup or 1,
|
||||||
}
|
}
|
||||||
if config.get().formatting.format then
|
if config.get().formatting.format then
|
||||||
vim_item = config.get().formatting.format(self, vim_item)
|
vim_item = config.get().formatting.format(self, vim_item)
|
||||||
@ -298,11 +300,11 @@ end
|
|||||||
---@return lsp.Range|nil
|
---@return lsp.Range|nil
|
||||||
entry.get_insert_range = function(self)
|
entry.get_insert_range = function(self)
|
||||||
local insert_range
|
local insert_range
|
||||||
if misc.safe(self.completion_item.textEdit) then
|
if misc.safe(self:get_completion_item().textEdit) then
|
||||||
if misc.safe(self.completion_item.textEdit.insert) then
|
if misc.safe(self:get_completion_item().textEdit.insert) then
|
||||||
insert_range = self.completion_item.textEdit.insert
|
insert_range = self:get_completion_item().textEdit.insert
|
||||||
else
|
else
|
||||||
insert_range = self.completion_item.textEdit.range
|
insert_range = self:get_completion_item().textEdit.range
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
insert_range = {
|
insert_range = {
|
||||||
@ -319,14 +321,10 @@ end
|
|||||||
---Return replace range
|
---Return replace range
|
||||||
---@return lsp.Range|nil
|
---@return lsp.Range|nil
|
||||||
entry.get_replace_range = function(self)
|
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
|
local replace_range
|
||||||
if misc.safe(self.completion_item.textEdit) then
|
if misc.safe(self:get_completion_item().textEdit) and misc.safe(self:get_completion_item().textEdit.replace) then
|
||||||
if misc.safe(self.completion_item.textEdit.replace) then
|
replace_range = self:get_completion_item().textEdit.replace
|
||||||
replace_range = self.completion_item.textEdit.replace
|
|
||||||
else
|
|
||||||
replace_range = self.completion_item.textEdit.range
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
replace_range = {
|
replace_range = {
|
||||||
start = {
|
start = {
|
||||||
@ -342,14 +340,49 @@ end
|
|||||||
|
|
||||||
---Match line.
|
---Match line.
|
||||||
---@param input string
|
---@param input string
|
||||||
|
---@param matching_config cmp.MatchingConfig
|
||||||
---@return { score: number, matches: table[] }
|
---@return { score: number, matches: table[] }
|
||||||
entry.match = function(self, input)
|
entry.match = function(self, input, matching_config)
|
||||||
return self.match_cache:ensure(input, function()
|
return self.match_cache:ensure({
|
||||||
|
input,
|
||||||
|
self.resolved_completion_item and 1 or 0,
|
||||||
|
matching_config.disallow_fuzzy_matching and 1 or 0,
|
||||||
|
matching_config.disallow_partial_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, _
|
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
|
if self:get_filter_text() ~= self:get_completion_item().label then
|
||||||
_, matches = matcher.match(input, self:get_completion_item().label, { self:get_word() })
|
_, matches = matcher.match(input, self:get_completion_item().label, { self:get_word() })
|
||||||
end
|
end
|
||||||
|
|
||||||
return { score = score, matches = matches }
|
return { score = score, matches = matches }
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@ -357,12 +390,12 @@ end
|
|||||||
---Get resolved completion item if possible.
|
---Get resolved completion item if possible.
|
||||||
---@return lsp.CompletionItem
|
---@return lsp.CompletionItem
|
||||||
entry.get_completion_item = function(self)
|
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
|
if self.resolved_completion_item then
|
||||||
local completion_item = misc.copy(self.completion_item)
|
local completion_item = misc.copy(self.completion_item)
|
||||||
completion_item.detail = self.resolved_completion_item.detail or completion_item.detail
|
for k, v in pairs(self.resolved_completion_item) do
|
||||||
completion_item.documentation = self.resolved_completion_item.documentation or completion_item.documentation
|
completion_item[k] = v or completion_item[k]
|
||||||
completion_item.additionalTextEdits = self.resolved_completion_item.additionalTextEdits or completion_item.additionalTextEdits
|
end
|
||||||
return completion_item
|
return completion_item
|
||||||
end
|
end
|
||||||
return self.completion_item
|
return self.completion_item
|
||||||
@ -378,9 +411,14 @@ entry.get_documentation = function(self)
|
|||||||
|
|
||||||
-- detail
|
-- detail
|
||||||
if misc.safe(item.detail) and item.detail ~= '' then
|
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, {
|
table.insert(documents, {
|
||||||
kind = types.lsp.MarkupKind.Markdown,
|
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
|
end
|
||||||
|
|
||||||
@ -399,7 +437,7 @@ end
|
|||||||
---Get completion item kind
|
---Get completion item kind
|
||||||
---@return lsp.CompletionItemKind
|
---@return lsp.CompletionItemKind
|
||||||
entry.get_kind = function(self)
|
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
|
end
|
||||||
|
|
||||||
---Execute completion item's command.
|
---Execute completion item's command.
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
local spec = require('cmp.utils.spec')
|
local spec = require('cmp.utils.spec')
|
||||||
|
local source = require('cmp.source')
|
||||||
|
local async = require('cmp.utils.async')
|
||||||
|
|
||||||
local entry = require('cmp.entry')
|
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_vim_item(4).word, '->foo')
|
||||||
assert.are.equal(e:get_filter_text(), '.foo')
|
assert.are.equal(e:get_filter_text(), 'foo')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[typescript-language-server] 1', function()
|
it('[typescript-language-server] 1', function()
|
||||||
@ -264,6 +266,65 @@ describe('entry', function()
|
|||||||
assert.are.equal(e:get_filter_text(), '$this')
|
assert.are.equal(e:get_filter_text(), '$this')
|
||||||
end)
|
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()
|
it('[#47] word should not contain \\n character', function()
|
||||||
local state = spec.state('', 1, 1)
|
local state = spec.state('', 1, 1)
|
||||||
|
|
||||||
|
@ -17,17 +17,33 @@ end
|
|||||||
cmp.lsp = require('cmp.types.lsp')
|
cmp.lsp = require('cmp.types.lsp')
|
||||||
cmp.vim = require('cmp.types.vim')
|
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 = {}
|
||||||
cmp.config.disable = misc.none
|
cmp.config.disable = misc.none
|
||||||
cmp.config.compare = require('cmp.config.compare')
|
cmp.config.compare = require('cmp.config.compare')
|
||||||
cmp.config.sources = require('cmp.config.sources')
|
cmp.config.sources = require('cmp.config.sources')
|
||||||
|
cmp.config.mapping = require('cmp.config.mapping')
|
||||||
|
|
||||||
---Expose event
|
---Sync asynchronous process.
|
||||||
cmp.event = cmp.core.event
|
cmp.sync = function(callback)
|
||||||
|
return function(...)
|
||||||
|
cmp.core.filter:sync(1000)
|
||||||
|
if callback then
|
||||||
|
return callback(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---Export mapping
|
---Suspend completion.
|
||||||
cmp.mapping = require('cmp.config.mapping')
|
cmp.suspend = function()
|
||||||
|
return cmp.core:suspend()
|
||||||
|
end
|
||||||
|
|
||||||
---Register completion sources
|
---Register completion sources
|
||||||
---@param name string
|
---@param name string
|
||||||
@ -52,28 +68,41 @@ cmp.get_config = function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Invoke completion manually
|
---Invoke completion manually
|
||||||
cmp.complete = function()
|
---@param option cmp.CompleteParams
|
||||||
cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.Manual }))
|
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
|
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.
|
---Return view is visible or not.
|
||||||
cmp.visible = function()
|
cmp.visible = cmp.sync(function()
|
||||||
return cmp.core.view:visible() or vim.fn.pumvisible() == 1
|
return cmp.core.view:visible() or vim.fn.pumvisible() == 1
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Get current selected entry or nil
|
---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()
|
return cmp.core.view:get_selected_entry()
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Get current active entry or nil
|
---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()
|
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
|
---Close current completion
|
||||||
cmp.close = function()
|
cmp.close = cmp.sync(function()
|
||||||
if cmp.core.view:visible() then
|
if cmp.core.view:visible() then
|
||||||
local release = cmp.core:suspend()
|
local release = cmp.core:suspend()
|
||||||
cmp.core.view:close()
|
cmp.core.view:close()
|
||||||
@ -83,10 +112,10 @@ cmp.close = function()
|
|||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Abort current completion
|
---Abort current completion
|
||||||
cmp.abort = function()
|
cmp.abort = cmp.sync(function()
|
||||||
if cmp.core.view:visible() then
|
if cmp.core.view:visible() then
|
||||||
local release = cmp.core:suspend()
|
local release = cmp.core:suspend()
|
||||||
cmp.core.view:abort()
|
cmp.core.view:abort()
|
||||||
@ -95,83 +124,65 @@ cmp.abort = function()
|
|||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Suspend completion.
|
|
||||||
cmp.suspend = function()
|
|
||||||
return cmp.core:suspend()
|
|
||||||
end
|
|
||||||
|
|
||||||
---Select next item if possible
|
---Select next item if possible
|
||||||
cmp.select_next_item = function(option)
|
cmp.select_next_item = cmp.sync(function(option)
|
||||||
option = option or {}
|
option = option or {}
|
||||||
|
|
||||||
-- Hack: Ignore when executing macro.
|
|
||||||
if vim.fn.reg_executing() ~= '' then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if cmp.core.view:visible() then
|
if cmp.core.view:visible() then
|
||||||
local release = cmp.core:suspend()
|
local release = cmp.core:suspend()
|
||||||
cmp.core.view:select_next_item(option)
|
cmp.core.view:select_next_item(option)
|
||||||
vim.schedule(release)
|
vim.schedule(release)
|
||||||
return true
|
return true
|
||||||
elseif vim.fn.pumvisible() == 1 then
|
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
|
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
|
else
|
||||||
feedkeys.call(keymap.t('<Down>'), 'n')
|
feedkeys.call(keymap.t('<Down>'), 'in')
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Select prev item if possible
|
---Select prev item if possible
|
||||||
cmp.select_prev_item = function(option)
|
cmp.select_prev_item = cmp.sync(function(option)
|
||||||
option = option or {}
|
option = option or {}
|
||||||
|
|
||||||
-- Hack: Ignore when executing macro.
|
|
||||||
if vim.fn.reg_executing() ~= '' then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if cmp.core.view:visible() then
|
if cmp.core.view:visible() then
|
||||||
local release = cmp.core:suspend()
|
local release = cmp.core:suspend()
|
||||||
cmp.core.view:select_prev_item(option)
|
cmp.core.view:select_prev_item(option)
|
||||||
vim.schedule(release)
|
vim.schedule(release)
|
||||||
return true
|
return true
|
||||||
elseif vim.fn.pumvisible() == 1 then
|
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
|
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
|
else
|
||||||
feedkeys.call(keymap.t('<Up>'), 'n')
|
feedkeys.call(keymap.t('<Up>'), 'in')
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Scrolling documentation window if possible
|
---Scrolling documentation window if possible
|
||||||
cmp.scroll_docs = function(delta)
|
cmp.scroll_docs = cmp.sync(function(delta)
|
||||||
if cmp.core.view:visible() then
|
if cmp.core.view:visible() then
|
||||||
cmp.core.view:scroll_docs(delta)
|
cmp.core.view:scroll_docs(delta)
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Confirm completion
|
---Confirm completion
|
||||||
cmp.confirm = function(option, callback)
|
cmp.confirm = cmp.sync(function(option, callback)
|
||||||
option = option or {}
|
option = option or {}
|
||||||
callback = callback or function() end
|
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)
|
local e = cmp.core.view:get_selected_entry() or (option.select and cmp.core.view:get_first_entry() or nil)
|
||||||
if e then
|
if e then
|
||||||
cmp.core:confirm(e, {
|
cmp.core:confirm(e, {
|
||||||
@ -182,13 +193,14 @@ cmp.confirm = function(option, callback)
|
|||||||
end)
|
end)
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
|
-- Special handling for native puma. Required to facilitate key mapping processing.
|
||||||
if vim.fn.complete_info({ 'selected' }).selected ~= -1 then
|
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
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
|
|
||||||
---Show status
|
---Show status
|
||||||
cmp.status = function()
|
cmp.status = function()
|
||||||
@ -255,6 +267,9 @@ cmp.setup = setmetatable({
|
|||||||
global = function(c)
|
global = function(c)
|
||||||
config.set_global(c)
|
config.set_global(c)
|
||||||
end,
|
end,
|
||||||
|
filetype = function(filetype, c)
|
||||||
|
config.set_filetype(c, filetype)
|
||||||
|
end,
|
||||||
buffer = function(c)
|
buffer = function(c)
|
||||||
config.set_buffer(c, vim.api.nvim_get_current_buf())
|
config.set_buffer(c, vim.api.nvim_get_current_buf())
|
||||||
end,
|
end,
|
||||||
@ -302,11 +317,29 @@ end)
|
|||||||
autocmd.subscribe('CursorMoved', function()
|
autocmd.subscribe('CursorMoved', function()
|
||||||
if config.enabled() then
|
if config.enabled() then
|
||||||
cmp.core:on_moved()
|
cmp.core:on_moved()
|
||||||
|
else
|
||||||
|
cmp.core:reset()
|
||||||
|
cmp.core.view:close()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
cmp.event:on('confirm_done', function(e)
|
autocmd.subscribe('InsertEnter', function()
|
||||||
cmp.config.compare.recently_used:add_entry(e)
|
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)
|
end)
|
||||||
|
|
||||||
return cmp
|
return cmp
|
||||||
|
@ -72,9 +72,11 @@ end
|
|||||||
---Match entry
|
---Match entry
|
||||||
---@param input string
|
---@param input string
|
||||||
---@param word 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
|
---@return number
|
||||||
matcher.match = function(input, word, words)
|
matcher.match = function(input, word, option)
|
||||||
|
option = option or {}
|
||||||
|
|
||||||
-- Empty input
|
-- Empty input
|
||||||
if #input == 0 then
|
if #input == 0 then
|
||||||
return matcher.PREFIX_FACTOR + matcher.NOT_FUZZY_FACTOR, {}
|
return matcher.PREFIX_FACTOR + matcher.NOT_FUZZY_FACTOR, {}
|
||||||
@ -85,7 +87,14 @@ matcher.match = function(input, word, words)
|
|||||||
return 0, {}
|
return 0, {}
|
||||||
end
|
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 matches = {}
|
||||||
local input_start_index = 1
|
local input_start_index = 1
|
||||||
local input_end_index = 1
|
local input_end_index = 1
|
||||||
@ -105,6 +114,11 @@ matcher.match = function(input, word, words)
|
|||||||
word_bound_index = word_bound_index + 1
|
word_bound_index = word_bound_index + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check partial matching.
|
||||||
|
if option.disallow_partial_matching and #matches > 1 then
|
||||||
|
return 0, {}
|
||||||
|
end
|
||||||
|
|
||||||
if #matches == 0 then
|
if #matches == 0 then
|
||||||
return 0, {}
|
return 0, {}
|
||||||
end
|
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
|
if matches[1].input_match_start == 1 and matches[1].word_match_start == 1 then
|
||||||
prefix = true
|
prefix = true
|
||||||
else
|
else
|
||||||
for _, w in ipairs(words or {}) do
|
for _, synonym in ipairs(option.synonyms or {}) do
|
||||||
prefix = true
|
prefix = true
|
||||||
local o = 1
|
local o = 1
|
||||||
for i = matches[1].input_match_start, matches[1].input_match_end do
|
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
|
prefix = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -152,8 +166,10 @@ matcher.match = function(input, word, words)
|
|||||||
|
|
||||||
-- Check remaining input as fuzzy
|
-- Check remaining input as fuzzy
|
||||||
if matches[#matches].input_match_end < #input then
|
if matches[#matches].input_match_end < #input then
|
||||||
if prefix and matcher.fuzzy(input, word, matches) then
|
if not option.disallow_fuzzy_matching then
|
||||||
return score, matches
|
if prefix and matcher.fuzzy(input, word, matches) then
|
||||||
|
return score, matches
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return 0, {}
|
return 0, {}
|
||||||
end
|
end
|
||||||
|
@ -16,20 +16,37 @@ describe('matcher', function()
|
|||||||
assert.is.truthy(matcher.match('woroff', 'word_offset') >= 1)
|
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('call', 'call') > matcher.match('call', 'condition_all'))
|
||||||
assert.is.truthy(matcher.match('Buffer', 'Buffer') > matcher.match('Buffer', 'buffer'))
|
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('fmodify', 'fnamemodify') >= 1)
|
||||||
assert.is.truthy(matcher.match('candlesingle', 'candle#accept#single') >= 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('vi', 'void#') >= 1)
|
||||||
assert.is.truthy(matcher.match('vo', '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('usela', 'useLayoutEffect') > matcher.match('usela', 'useDataLayer'))
|
||||||
assert.is.truthy(matcher.match('true', 'v:true', { 'true' }) == matcher.match('true', 'true'))
|
assert.is.truthy(matcher.match('my_', 'my_awesome_variable') > matcher.match('my_', 'completion_matching_strategy_list'))
|
||||||
assert.is.truthy(matcher.match('g', 'get', { 'get' }) > matcher.match('g', 'dein#get', { 'dein#get' }))
|
|
||||||
assert.is.truthy(matcher.match('2', '[[2021') >= 1)
|
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)
|
end)
|
||||||
|
|
||||||
it('debug', function()
|
it('debug', function()
|
||||||
|
@ -15,12 +15,13 @@ local char = require('cmp.utils.char')
|
|||||||
---@field public source any
|
---@field public source any
|
||||||
---@field public cache cmp.Cache
|
---@field public cache cmp.Cache
|
||||||
---@field public revision number
|
---@field public revision number
|
||||||
---@field public context cmp.Context
|
|
||||||
---@field public incomplete boolean
|
---@field public incomplete boolean
|
||||||
---@field public is_triggered_by_symbol boolean
|
---@field public is_triggered_by_symbol boolean
|
||||||
---@field public entries cmp.Entry[]
|
---@field public entries cmp.Entry[]
|
||||||
---@field public offset number
|
---@field public offset number
|
||||||
---@field public request_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 status cmp.SourceStatus
|
||||||
---@field public complete_dedup function
|
---@field public complete_dedup function
|
||||||
local source = {}
|
local source = {}
|
||||||
@ -50,21 +51,28 @@ source.reset = function(self)
|
|||||||
self.cache:clear()
|
self.cache:clear()
|
||||||
self.revision = self.revision + 1
|
self.revision = self.revision + 1
|
||||||
self.context = context.empty()
|
self.context = context.empty()
|
||||||
self.request_offset = -1
|
|
||||||
self.is_triggered_by_symbol = false
|
self.is_triggered_by_symbol = false
|
||||||
self.incomplete = false
|
self.incomplete = false
|
||||||
self.entries = {}
|
self.entries = {}
|
||||||
self.offset = -1
|
self.offset = -1
|
||||||
|
self.request_offset = -1
|
||||||
|
self.completion_context = nil
|
||||||
self.status = source.SourceStatus.WAITING
|
self.status = source.SourceStatus.WAITING
|
||||||
self.complete_dedup(function() end)
|
self.complete_dedup(function() end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return source option
|
---Return source config
|
||||||
---@return cmp.SourceConfig
|
---@return cmp.SourceConfig
|
||||||
source.get_config = function(self)
|
source.get_source_config = function(self)
|
||||||
return config.get_source_config(self.name) or {}
|
return config.get_source_config(self.name) or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Return matching config
|
||||||
|
---@return cmp.MatchingConfig
|
||||||
|
source.get_matching_config = function()
|
||||||
|
return config.get().matching
|
||||||
|
end
|
||||||
|
|
||||||
---Get fetching time
|
---Get fetching time
|
||||||
source.get_fetching_time = function(self)
|
source.get_fetching_time = function(self)
|
||||||
if self.status == source.SourceStatus.FETCHING then
|
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)
|
inputs[o] = string.sub(ctx.cursor_before_line, o)
|
||||||
end
|
end
|
||||||
|
|
||||||
local match = e:match(inputs[o])
|
local match = e:match(inputs[o], self:get_matching_config())
|
||||||
e.score = match.score
|
e.score = match.score
|
||||||
e.exact = false
|
e.exact = false
|
||||||
if e.score >= 1 then
|
if e.score >= 1 then
|
||||||
@ -112,7 +120,7 @@ source.get_entries = function(self, ctx)
|
|||||||
end
|
end
|
||||||
self.cache:set({ 'get_entries', self.revision, ctx.cursor_before_line }, entries)
|
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 = {}
|
local limited_entries = {}
|
||||||
for _, e in ipairs(entries) do
|
for _, e in ipairs(entries) do
|
||||||
table.insert(limited_entries, e)
|
table.insert(limited_entries, e)
|
||||||
@ -183,17 +191,33 @@ source.is_available = function(self)
|
|||||||
return true
|
return true
|
||||||
end
|
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
|
---Get keyword_pattern
|
||||||
---@return string
|
---@return string
|
||||||
source.get_keyword_pattern = function(self)
|
source.get_keyword_pattern = function(self)
|
||||||
local c = self:get_config()
|
local c = self:get_source_config()
|
||||||
if c.keyword_pattern then
|
if c.keyword_pattern then
|
||||||
return c.keyword_pattern
|
return c.keyword_pattern
|
||||||
end
|
end
|
||||||
if self.source.get_keyword_pattern then
|
if self.source.get_keyword_pattern then
|
||||||
return self.source:get_keyword_pattern({
|
return self.source:get_keyword_pattern(misc.copy(c))
|
||||||
option = self:get_config().opts,
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
return config.get().completion.keyword_pattern
|
return config.get().completion.keyword_pattern
|
||||||
end
|
end
|
||||||
@ -201,48 +225,28 @@ end
|
|||||||
---Get keyword_length
|
---Get keyword_length
|
||||||
---@return number
|
---@return number
|
||||||
source.get_keyword_length = function(self)
|
source.get_keyword_length = function(self)
|
||||||
local c = self:get_config()
|
local c = self:get_source_config()
|
||||||
if c.keyword_length then
|
if c.keyword_length then
|
||||||
return c.keyword_length
|
return c.keyword_length
|
||||||
end
|
end
|
||||||
return config.get().completion.keyword_length or 1
|
return config.get().completion.keyword_length or 1
|
||||||
end
|
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
|
---Invoke completion
|
||||||
---@param ctx cmp.Context
|
---@param ctx cmp.Context
|
||||||
---@param callback function
|
---@param callback function
|
||||||
---@return boolean Return true if not trigger completion.
|
---@return boolean Return true if not trigger completion.
|
||||||
source.complete = function(self, ctx, callback)
|
source.complete = function(self, ctx, callback)
|
||||||
local offset = ctx:get_offset(self:get_keyword_pattern())
|
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 = 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 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 = ''
|
before_char = ''
|
||||||
end
|
end
|
||||||
if string.match(before_char_iw, '^%a+$') then
|
|
||||||
before_char_iw = ''
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local completion_context
|
local completion_context
|
||||||
@ -250,39 +254,31 @@ source.complete = function(self, ctx, callback)
|
|||||||
completion_context = {
|
completion_context = {
|
||||||
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
||||||
}
|
}
|
||||||
else
|
elseif vim.tbl_contains(self:get_trigger_characters(), before_char) then
|
||||||
if vim.tbl_contains(self:get_trigger_characters(), before_char) then
|
completion_context = {
|
||||||
completion_context = {
|
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
||||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
triggerCharacter = before_char,
|
||||||
triggerCharacter = before_char,
|
}
|
||||||
}
|
elseif ctx:get_reason() ~= types.cmp.ContextReason.TriggerOnly then
|
||||||
elseif vim.tbl_contains(self:get_trigger_characters(), before_char_iw) then
|
if self:get_keyword_length() <= (ctx.cursor.col - offset) then
|
||||||
completion_context = {
|
if self.incomplete and self.context.cursor.col ~= ctx.cursor.col and self.status ~= source.SourceStatus.FETCHING then
|
||||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
completion_context = {
|
||||||
triggerCharacter = before_char_iw,
|
triggerKind = types.lsp.CompletionTriggerKind.TriggerForIncompleteCompletions,
|
||||||
}
|
}
|
||||||
elseif ctx:get_reason() ~= types.cmp.ContextReason.TriggerOnly then
|
elseif not vim.tbl_contains({ self.request_offset, self.offset }, offset) then
|
||||||
if self:get_keyword_length() <= (ctx.cursor.col - offset) then
|
completion_context = {
|
||||||
if self.incomplete and self.context.cursor.col ~= ctx.cursor.col then
|
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
||||||
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
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
self:reset()
|
self:reset() -- Should clear current completion if the TriggerKind isn't TriggerCharacter or Manual and keyword length does not enough.
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
self:reset() -- Should clear current completion if ContextReason is TriggerOnly and the triggerCharacter isn't matched
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Does not perform completions.
|
||||||
if not completion_context then
|
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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -293,16 +289,16 @@ source.complete = function(self, ctx, callback)
|
|||||||
debug.log(self:get_debug_name(), 'request', offset, vim.inspect(completion_context))
|
debug.log(self:get_debug_name(), 'request', offset, vim.inspect(completion_context))
|
||||||
local prev_status = self.status
|
local prev_status = self.status
|
||||||
self.status = source.SourceStatus.FETCHING
|
self.status = source.SourceStatus.FETCHING
|
||||||
self.request_offset = offset
|
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
self.request_offset = offset
|
||||||
self.context = ctx
|
self.context = ctx
|
||||||
|
self.completion_context = completion_context
|
||||||
self.source:complete(
|
self.source:complete(
|
||||||
{
|
vim.tbl_extend('keep', misc.copy(self:get_source_config()), {
|
||||||
context = ctx,
|
|
||||||
offset = self.offset,
|
offset = self.offset,
|
||||||
option = self:get_config().opts,
|
context = ctx,
|
||||||
completion_context = completion_context,
|
completion_context = completion_context,
|
||||||
},
|
}),
|
||||||
self.complete_dedup(vim.schedule_wrap(function(response)
|
self.complete_dedup(vim.schedule_wrap(function(response)
|
||||||
response = response or {}
|
response = response or {}
|
||||||
|
|
||||||
@ -329,8 +325,9 @@ source.complete = function(self, ctx, callback)
|
|||||||
self.revision = self.revision + 1
|
self.revision = self.revision + 1
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
debug.log(self:get_debug_name(), 'continue', 'nil')
|
-- The completion will be invoked when pressing <CR> if the trigger characters contain the <Space>.
|
||||||
if completion_context.triggerKind == types.lsp.CompletionTriggerKind.TriggerCharacter then
|
-- 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()
|
self:reset()
|
||||||
end
|
end
|
||||||
self.status = prev_status
|
self.status = prev_status
|
||||||
|
@ -38,6 +38,7 @@ cmp.ItemField.Menu = 'menu'
|
|||||||
|
|
||||||
---@class cmp.ConfirmOption
|
---@class cmp.ConfirmOption
|
||||||
---@field public behavior cmp.ConfirmBehavior
|
---@field public behavior cmp.ConfirmBehavior
|
||||||
|
---@field public commit_character? string
|
||||||
|
|
||||||
---@class cmp.SelectOption
|
---@class cmp.SelectOption
|
||||||
---@field public behavior cmp.SelectBehavior
|
---@field public behavior cmp.SelectBehavior
|
||||||
@ -46,18 +47,21 @@ cmp.ItemField.Menu = 'menu'
|
|||||||
---@field public body string
|
---@field public body string
|
||||||
---@field public insert_text_mode number
|
---@field public insert_text_mode number
|
||||||
|
|
||||||
|
---@class cmp.CompleteParams
|
||||||
|
---@field public reason? cmp.ContextReason
|
||||||
|
---@field public config? cmp.ConfigSchema
|
||||||
|
|
||||||
---@class cmp.Setup
|
---@class cmp.Setup
|
||||||
---@field public __call fun(c: cmp.ConfigSchema)
|
---@field public __call fun(c: cmp.ConfigSchema)
|
||||||
---@field public buffer fun(c: cmp.ConfigSchema)
|
---@field public buffer fun(c: cmp.ConfigSchema)
|
||||||
---@field public global fun(c: cmp.ConfigSchema)
|
---@field public global fun(c: cmp.ConfigSchema)
|
||||||
---@field public cmdline fun(type: string, c: cmp.ConfigSchema)
|
---@field public cmdline fun(type: string, c: cmp.ConfigSchema)
|
||||||
|
|
||||||
---@class cmp.SourceBaseApiParams
|
---@class cmp.SourceApiParams: cmp.SourceConfig
|
||||||
---@field public option table
|
|
||||||
|
|
||||||
---@class cmp.SourceCompletionApiParams : cmp.SourceBaseApiParams
|
---@class cmp.SourceCompletionApiParams : cmp.SourceConfig
|
||||||
---@field public context cmp.Context
|
|
||||||
---@field public offset number
|
---@field public offset number
|
||||||
|
---@field public context cmp.Context
|
||||||
---@field public completion_context lsp.CompletionContext
|
---@field public completion_context lsp.CompletionContext
|
||||||
|
|
||||||
---@class cmp.Mapping
|
---@class cmp.Mapping
|
||||||
@ -73,11 +77,13 @@ cmp.ItemField.Menu = 'menu'
|
|||||||
---@field public completion cmp.CompletionConfig
|
---@field public completion cmp.CompletionConfig
|
||||||
---@field public documentation cmp.DocumentationConfig|"false"
|
---@field public documentation cmp.DocumentationConfig|"false"
|
||||||
---@field public confirmation cmp.ConfirmationConfig
|
---@field public confirmation cmp.ConfirmationConfig
|
||||||
|
---@field public matching cmp.MatchingConfig
|
||||||
---@field public sorting cmp.SortingConfig
|
---@field public sorting cmp.SortingConfig
|
||||||
---@field public formatting cmp.FormattingConfig
|
---@field public formatting cmp.FormattingConfig
|
||||||
---@field public snippet cmp.SnippetConfig
|
---@field public snippet cmp.SnippetConfig
|
||||||
---@field public mapping table<string, cmp.Mapping>
|
---@field public mapping table<string, cmp.Mapping>
|
||||||
---@field public sources cmp.SourceConfig[]
|
---@field public sources cmp.SourceConfig[]
|
||||||
|
---@field public view cmp.ViewConfig
|
||||||
---@field public experimental cmp.ExperimentalConfig
|
---@field public experimental cmp.ExperimentalConfig
|
||||||
|
|
||||||
---@class cmp.CompletionConfig
|
---@class cmp.CompletionConfig
|
||||||
@ -98,6 +104,11 @@ cmp.ItemField.Menu = 'menu'
|
|||||||
---@field public default_behavior cmp.ConfirmBehavior
|
---@field public default_behavior cmp.ConfirmBehavior
|
||||||
---@field public get_commit_characters fun(commit_characters: string[]): string[]
|
---@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
|
---@class cmp.SortingConfig
|
||||||
---@field public priority_weight number
|
---@field public priority_weight number
|
||||||
---@field public comparators function[]
|
---@field public comparators function[]
|
||||||
@ -118,11 +129,28 @@ cmp.ItemField.Menu = 'menu'
|
|||||||
|
|
||||||
---@class cmp.SourceConfig
|
---@class cmp.SourceConfig
|
||||||
---@field public name string
|
---@field public name string
|
||||||
---@field public opts table
|
---@field public option table|nil
|
||||||
---@field public priority number|nil
|
---@field public priority number|nil
|
||||||
---@field public keyword_pattern string
|
---@field public trigger_characters string[]|nil
|
||||||
---@field public keyword_length number
|
---@field public keyword_pattern string|nil
|
||||||
---@field public max_item_count number
|
---@field public keyword_length number|nil
|
||||||
---@field public group_index number
|
---@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
|
return cmp
|
||||||
|
@ -26,7 +26,7 @@ lsp.Position.to_vim = function(buf, position)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
---Convert lsp.Position to vim.Position
|
---Convert vim.Position to lsp.Position
|
||||||
---@param buf number|string
|
---@param buf number|string
|
||||||
---@param position vim.Position
|
---@param position vim.Position
|
||||||
---@return lsp.Position
|
---@return lsp.Position
|
||||||
@ -49,7 +49,7 @@ end
|
|||||||
|
|
||||||
lsp.Range = {}
|
lsp.Range = {}
|
||||||
|
|
||||||
---Convert lsp.Position to vim.Position
|
---Convert lsp.Range to vim.Range
|
||||||
---@param buf number|string
|
---@param buf number|string
|
||||||
---@param range lsp.Range
|
---@param range lsp.Range
|
||||||
---@return vim.Range
|
---@return vim.Range
|
||||||
@ -60,7 +60,7 @@ lsp.Range.to_vim = function(buf, range)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
---Convert lsp.Position to vim.Position
|
---Convert vim.Range to lsp.Range
|
||||||
---@param buf number|string
|
---@param buf number|string
|
||||||
---@param range vim.Range
|
---@param range vim.Range
|
||||||
---@return lsp.Range
|
---@return lsp.Range
|
||||||
@ -97,7 +97,6 @@ lsp.InsertTextMode = vim.tbl_add_reverse_lookup(lsp.InsertTextMode)
|
|||||||
lsp.MarkupKind = {}
|
lsp.MarkupKind = {}
|
||||||
lsp.MarkupKind.PlainText = 'plaintext'
|
lsp.MarkupKind.PlainText = 'plaintext'
|
||||||
lsp.MarkupKind.Markdown = 'markdown'
|
lsp.MarkupKind.Markdown = 'markdown'
|
||||||
lsp.MarkupKind.Markdown = 'markdown'
|
|
||||||
lsp.MarkupKind = vim.tbl_add_reverse_lookup(lsp.MarkupKind)
|
lsp.MarkupKind = vim.tbl_add_reverse_lookup(lsp.MarkupKind)
|
||||||
|
|
||||||
---@alias lsp.CompletionItemTag "1"
|
---@alias lsp.CompletionItemTag "1"
|
||||||
|
@ -11,35 +11,36 @@ describe('types.lsp', function()
|
|||||||
})
|
})
|
||||||
local vim_position, lsp_position
|
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.row, 2)
|
||||||
assert.are.equal(vim_position.col, 10)
|
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.line, 1)
|
||||||
assert.are.equal(lsp_position.character, 3)
|
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.row, 2)
|
||||||
assert.are.equal(vim_position.col, 1)
|
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.line, 1)
|
||||||
assert.are.equal(lsp_position.character, 0)
|
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.row, 2)
|
||||||
assert.are.equal(vim_position.col, 16)
|
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.line, 1)
|
||||||
assert.are.equal(lsp_position.character, 5)
|
assert.are.equal(lsp_position.character, 5)
|
||||||
|
|
||||||
-- overflow (lsp -> vim)
|
-- 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.row, 2)
|
||||||
assert.are.equal(vim_position.col, 16)
|
assert.are.equal(vim_position.col, 16)
|
||||||
|
|
||||||
-- overflow(vim -> lsp)
|
-- overflow(vim -> lsp)
|
||||||
vim_position.col = vim_position.col + 1
|
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.line, 1)
|
||||||
assert.are.equal(lsp_position.character, 5)
|
assert.are.equal(lsp_position.character, 5)
|
||||||
end)
|
end)
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
---@field public empty "1"|nil
|
---@field public empty "1"|nil
|
||||||
---@field public dup "1"|nil
|
---@field public dup "1"|nil
|
||||||
---@field public id any
|
---@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
|
---@class vim.Position
|
||||||
---@field public row number
|
---@field public row number
|
||||||
|
@ -53,7 +53,8 @@ end
|
|||||||
|
|
||||||
api.get_screen_cursor = function()
|
api.get_screen_cursor = function()
|
||||||
if api.is_cmdline_mode() then
|
if api.is_cmdline_mode() then
|
||||||
return api.get_cursor()
|
local cursor = api.get_cursor()
|
||||||
|
return { cursor[1], cursor[2] + 1 }
|
||||||
end
|
end
|
||||||
local cursor = api.get_cursor()
|
local cursor = api.get_cursor()
|
||||||
local pos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1)
|
local pos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
local async = {}
|
local async = {}
|
||||||
|
|
||||||
---@class cmp.AsyncThrottle
|
---@class cmp.AsyncThrottle
|
||||||
|
---@field public running boolean
|
||||||
---@field public timeout number
|
---@field public timeout number
|
||||||
|
---@field public sync function(self: cmp.AsyncThrottle, timeout: number|nil)
|
||||||
---@field public stop function
|
---@field public stop function
|
||||||
---@field public __call function
|
---@field public __call function
|
||||||
|
|
||||||
@ -12,7 +14,13 @@ async.throttle = function(fn, timeout)
|
|||||||
local time = nil
|
local time = nil
|
||||||
local timer = vim.loop.new_timer()
|
local timer = vim.loop.new_timer()
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
|
running = false,
|
||||||
timeout = timeout,
|
timeout = timeout,
|
||||||
|
sync = function(self, timeout_)
|
||||||
|
vim.wait(timeout_ or 1000, function()
|
||||||
|
return not self.running
|
||||||
|
end)
|
||||||
|
end,
|
||||||
stop = function()
|
stop = function()
|
||||||
time = nil
|
time = nil
|
||||||
timer:stop()
|
timer:stop()
|
||||||
@ -24,12 +32,15 @@ async.throttle = function(fn, timeout)
|
|||||||
if time == nil then
|
if time == nil then
|
||||||
time = vim.loop.now()
|
time = vim.loop.now()
|
||||||
end
|
end
|
||||||
timer:stop()
|
|
||||||
|
|
||||||
local delta = math.max(1, self.timeout - (vim.loop.now() - time))
|
self.running = true
|
||||||
timer:start(delta, 0, function()
|
timer:stop()
|
||||||
time = nil
|
timer:start(math.max(1, self.timeout - (vim.loop.now() - time)), 0, function()
|
||||||
fn(unpack(args))
|
vim.schedule(function()
|
||||||
|
time = nil
|
||||||
|
fn(unpack(args))
|
||||||
|
self.running = false
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
@ -1,17 +1,30 @@
|
|||||||
local buffer = {}
|
local buffer = {}
|
||||||
|
|
||||||
buffer.ensure = setmetatable({
|
buffer.cache = {}
|
||||||
cache = {},
|
|
||||||
}, {
|
---@return number buf
|
||||||
__call = function(self, name)
|
buffer.get = function(name)
|
||||||
if not (self.cache[name] and vim.api.nvim_buf_is_valid(self.cache[name])) then
|
local buf = buffer.cache[name]
|
||||||
local buf = vim.api.nvim_create_buf(false, true)
|
if buf and vim.api.nvim_buf_is_valid(buf) then
|
||||||
vim.api.nvim_buf_set_option(buf, 'buftype', 'nofile')
|
return buf
|
||||||
vim.api.nvim_buf_set_option(buf, 'bufhidden', 'hide')
|
else
|
||||||
self.cache[name] = buf
|
return nil
|
||||||
end
|
end
|
||||||
return self.cache[name]
|
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
|
return buffer
|
||||||
|
@ -23,15 +23,6 @@ describe('feedkeys', function()
|
|||||||
})
|
})
|
||||||
end)
|
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()
|
it('testability', function()
|
||||||
feedkeys.call('i', 'n', function()
|
feedkeys.call('i', 'n', function()
|
||||||
feedkeys.call('', 'n', function()
|
feedkeys.call('', 'n', function()
|
||||||
|
@ -10,7 +10,7 @@ highlight.keys = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
highlight.inherit = function(name, source, override)
|
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
|
for _, key in ipairs(highlight.keys) do
|
||||||
if override[key] then
|
if override[key] then
|
||||||
cmd = cmd .. (' %s=%s'):format(key, override[key])
|
cmd = cmd .. (' %s=%s'):format(key, override[key])
|
||||||
|
@ -7,7 +7,9 @@ local keymap = {}
|
|||||||
---@param keys string
|
---@param keys string
|
||||||
---@return string
|
---@return string
|
||||||
keymap.t = function(keys)
|
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
|
end
|
||||||
|
|
||||||
---Normalize key sequence.
|
---Normalize key sequence.
|
||||||
@ -65,6 +67,9 @@ end
|
|||||||
---@param count number
|
---@param count number
|
||||||
---@return string
|
---@return string
|
||||||
keymap.backspace = function(count)
|
keymap.backspace = function(count)
|
||||||
|
if type(count) == 'string' then
|
||||||
|
count = vim.fn.strchars(count, true)
|
||||||
|
end
|
||||||
if count <= 0 then
|
if count <= 0 then
|
||||||
return ''
|
return ''
|
||||||
end
|
end
|
||||||
@ -73,16 +78,11 @@ keymap.backspace = function(count)
|
|||||||
return table.concat(keys, '')
|
return table.concat(keys, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
---Create autoindent keys
|
---Update indentkeys.
|
||||||
|
---@param expr string
|
||||||
---@return string
|
---@return string
|
||||||
keymap.autoindent = function()
|
keymap.indentkeys = function(expr)
|
||||||
local keys = {}
|
return string.format(keymap.t('<Cmd>set indentkeys=%s<CR>'), expr and vim.fn.escape(expr, '| \t\\') or '')
|
||||||
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, '')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return two key sequence are equal or not.
|
---Return two key sequence are equal or not.
|
||||||
@ -97,25 +97,22 @@ end
|
|||||||
keymap.listen = function(mode, lhs, callback)
|
keymap.listen = function(mode, lhs, callback)
|
||||||
lhs = keymap.normalize(keymap.to_keymap(lhs))
|
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+)%)')
|
local id = string.match(existing.rhs, 'v:lua%.cmp%.utils%.keymap%.set_map%((%d+)%)')
|
||||||
if id and keymap.set_map.callbacks[tonumber(id, 10)] then
|
if id and keymap.set_map.callbacks[tonumber(id, 10)] then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local bufnr = existing.buffer and vim.api.nvim_get_current_buf() or -1
|
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()
|
keymap.set_map(bufnr, mode, lhs, function()
|
||||||
if mode == 'c' and vim.fn.getcmdtype() == '=' then
|
local ignore = false
|
||||||
return vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true)
|
ignore = ignore or (mode == 'c' and vim.fn.getcmdtype() == '=')
|
||||||
|
if ignore then
|
||||||
|
fallback()
|
||||||
|
else
|
||||||
|
callback(lhs, misc.once(fallback))
|
||||||
end
|
end
|
||||||
|
|
||||||
callback(
|
|
||||||
lhs,
|
|
||||||
misc.once(function()
|
|
||||||
vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true)
|
|
||||||
end)
|
|
||||||
)
|
|
||||||
end, {
|
end, {
|
||||||
expr = false,
|
expr = false,
|
||||||
noremap = true,
|
noremap = true,
|
||||||
@ -123,19 +120,66 @@ keymap.listen = function(mode, lhs, callback)
|
|||||||
})
|
})
|
||||||
end
|
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 mode string
|
||||||
---@param lhs string
|
---@param lhs string
|
||||||
---@return table
|
---@return table
|
||||||
keymap.get_mapping = function(mode, lhs)
|
keymap.get_map = function(mode, lhs)
|
||||||
lhs = keymap.normalize(lhs)
|
lhs = keymap.normalize(lhs)
|
||||||
|
|
||||||
for _, map in ipairs(vim.api.nvim_buf_get_keymap(0, mode)) do
|
for _, map in ipairs(vim.api.nvim_buf_get_keymap(0, mode)) do
|
||||||
if keymap.equals(map.lhs, lhs) then
|
if keymap.equals(map.lhs, lhs) then
|
||||||
return {
|
return {
|
||||||
lhs = map.lhs,
|
lhs = map.lhs,
|
||||||
rhs = map.rhs,
|
rhs = map.rhs or '',
|
||||||
expr = map.expr == 1,
|
expr = map.expr == 1,
|
||||||
|
callback = map.callback,
|
||||||
noremap = map.noremap == 1,
|
noremap = map.noremap == 1,
|
||||||
script = map.script == 1,
|
script = map.script == 1,
|
||||||
silent = map.silent == 1,
|
silent = map.silent == 1,
|
||||||
@ -149,8 +193,9 @@ keymap.get_mapping = function(mode, lhs)
|
|||||||
if keymap.equals(map.lhs, lhs) then
|
if keymap.equals(map.lhs, lhs) then
|
||||||
return {
|
return {
|
||||||
lhs = map.lhs,
|
lhs = map.lhs,
|
||||||
rhs = map.rhs,
|
rhs = map.rhs or '',
|
||||||
expr = map.expr == 1,
|
expr = map.expr == 1,
|
||||||
|
callback = map.callback,
|
||||||
noremap = map.noremap == 1,
|
noremap = map.noremap == 1,
|
||||||
script = map.script == 1,
|
script = map.script == 1,
|
||||||
silent = map.silent == 1,
|
silent = map.silent == 1,
|
||||||
@ -164,80 +209,15 @@ keymap.get_mapping = function(mode, lhs)
|
|||||||
lhs = lhs,
|
lhs = lhs,
|
||||||
rhs = lhs,
|
rhs = lhs,
|
||||||
expr = false,
|
expr = false,
|
||||||
|
callback = nil,
|
||||||
noremap = true,
|
noremap = true,
|
||||||
script = false,
|
script = false,
|
||||||
silent = false,
|
silent = true,
|
||||||
nowait = false,
|
nowait = false,
|
||||||
buffer = false,
|
buffer = false,
|
||||||
}
|
}
|
||||||
end
|
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
|
---Set keymapping
|
||||||
keymap.set_map = setmetatable({
|
keymap.set_map = setmetatable({
|
||||||
callbacks = {},
|
callbacks = {},
|
||||||
|
@ -1,68 +1,159 @@
|
|||||||
local spec = require('cmp.utils.spec')
|
local spec = require('cmp.utils.spec')
|
||||||
|
local api = require('cmp.utils.api')
|
||||||
|
local feedkeys = require('cmp.utils.feedkeys')
|
||||||
|
|
||||||
local keymap = require('cmp.utils.keymap')
|
local keymap = require('cmp.utils.keymap')
|
||||||
|
|
||||||
describe('keymap', function()
|
describe('keymap', function()
|
||||||
before_each(spec.before)
|
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()
|
it('to_keymap', function()
|
||||||
assert.are.equal(keymap.to_keymap('\n'), '<CR>')
|
assert.are.equal(keymap.to_keymap('\n'), '<CR>')
|
||||||
assert.are.equal(keymap.to_keymap('<CR>'), '<CR>')
|
assert.are.equal(keymap.to_keymap('<CR>'), '<CR>')
|
||||||
assert.are.equal(keymap.to_keymap('|'), '<Bar>')
|
assert.are.equal(keymap.to_keymap('|'), '<Bar>')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('evacuate', function()
|
describe('fallback', function()
|
||||||
before_each(spec.before)
|
before_each(spec.before)
|
||||||
|
|
||||||
it('expr & register', function()
|
local run_fallback = function(keys, fallback)
|
||||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', [['<C-r>="("<CR>']], {
|
local state = {}
|
||||||
expr = true,
|
feedkeys.call(keys, '', function()
|
||||||
noremap = false,
|
fallback()
|
||||||
})
|
|
||||||
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))
|
|
||||||
end)
|
end)
|
||||||
it('false', function()
|
feedkeys.call('', '', function()
|
||||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Tab>', [[v:false ? '<C-r>="foobar"<CR>' : '<Tab>aiueo']], {
|
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,
|
expr = true,
|
||||||
noremap = false,
|
noremap = false,
|
||||||
|
silent = true,
|
||||||
|
callback = function()
|
||||||
|
return '()' .. keymap.t('<Left>')
|
||||||
|
end,
|
||||||
})
|
})
|
||||||
local fallback = keymap.evacuate(0, 'i', '<Tab>')
|
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
local state = run_fallback('i', fallback)
|
||||||
assert.are.same({ '\taiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
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)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('realworld', function()
|
describe('realworld', function()
|
||||||
before_each(spec.before)
|
before_each(spec.before)
|
||||||
|
|
||||||
it('#226', function()
|
it('#226', function()
|
||||||
keymap.listen('i', '<c-n>', function(_, fallback)
|
keymap.listen('i', '<c-n>', function(_, fallback)
|
||||||
fallback()
|
fallback()
|
||||||
@ -70,6 +161,7 @@ describe('keymap', function()
|
|||||||
vim.api.nvim_feedkeys(keymap.t('iaiueo<CR>a<C-n><C-n>'), 'tx', true)
|
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))
|
assert.are.same({ 'aiueo', 'aiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('#414', function()
|
it('#414', function()
|
||||||
keymap.listen('i', '<M-j>', function()
|
keymap.listen('i', '<M-j>', function()
|
||||||
vim.api.nvim_feedkeys(keymap.t('<C-n>'), 'int', true)
|
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)
|
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))
|
assert.are.same({ 'aiueo', 'aiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||||
end)
|
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)
|
||||||
end)
|
end)
|
||||||
|
@ -29,6 +29,28 @@ misc.concat = function(list1, list2)
|
|||||||
return new_list
|
return new_list
|
||||||
end
|
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.
|
---The symbol to remove key in misc.merge.
|
||||||
misc.none = vim.NIL
|
misc.none = vim.NIL
|
||||||
|
|
||||||
@ -77,7 +99,7 @@ misc.id = setmetatable({
|
|||||||
group = {},
|
group = {},
|
||||||
}, {
|
}, {
|
||||||
__call = function(_, 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
|
misc.id.group[group] = misc.id.group[group] + 1
|
||||||
return misc.id.group[group]
|
return misc.id.group[group]
|
||||||
end,
|
end,
|
||||||
@ -144,9 +166,10 @@ end
|
|||||||
|
|
||||||
---Safe version of vim.str_utfindex
|
---Safe version of vim.str_utfindex
|
||||||
---@param text string
|
---@param text string
|
||||||
---@param vimindex number
|
---@param vimindex number|nil
|
||||||
---@return number
|
---@return number
|
||||||
misc.to_utfindex = function(text, vimindex)
|
misc.to_utfindex = function(text, vimindex)
|
||||||
|
vimindex = vimindex or #text + 1
|
||||||
return vim.str_utfindex(text, math.max(0, math.min(vimindex - 1, #text)))
|
return vim.str_utfindex(text, math.max(0, math.min(vimindex - 1, #text)))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -155,6 +178,7 @@ end
|
|||||||
---@param utfindex number
|
---@param utfindex number
|
||||||
---@return number
|
---@return number
|
||||||
misc.to_vimindex = function(text, utfindex)
|
misc.to_vimindex = function(text, utfindex)
|
||||||
|
utfindex = utfindex or #text
|
||||||
for i = utfindex, 1, -1 do
|
for i = utfindex, 1, -1 do
|
||||||
local s, v = pcall(function()
|
local s, v = pcall(function()
|
||||||
return vim.str_byteindex(text, i) + 1
|
return vim.str_byteindex(text, i) + 1
|
||||||
@ -178,4 +202,34 @@ misc.deprecated = function(fn, msg)
|
|||||||
end
|
end
|
||||||
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
|
return misc
|
||||||
|
@ -3,24 +3,29 @@ local pattern = require('cmp.utils.pattern')
|
|||||||
|
|
||||||
local str = {}
|
local str = {}
|
||||||
|
|
||||||
local INVALID_CHARS = {}
|
local INVALIDS = {}
|
||||||
INVALID_CHARS[string.byte("'")] = true
|
INVALIDS[string.byte("'")] = true
|
||||||
INVALID_CHARS[string.byte('"')] = true
|
INVALIDS[string.byte('"')] = true
|
||||||
INVALID_CHARS[string.byte('=')] = true
|
INVALIDS[string.byte('=')] = true
|
||||||
INVALID_CHARS[string.byte('$')] = true
|
INVALIDS[string.byte('$')] = true
|
||||||
INVALID_CHARS[string.byte('(')] = true
|
INVALIDS[string.byte('(')] = true
|
||||||
INVALID_CHARS[string.byte('[')] = true
|
INVALIDS[string.byte('[')] = true
|
||||||
INVALID_CHARS[string.byte(' ')] = true
|
INVALIDS[string.byte('<')] = true
|
||||||
INVALID_CHARS[string.byte('\t')] = true
|
INVALIDS[string.byte('{')] = true
|
||||||
INVALID_CHARS[string.byte('\n')] = true
|
INVALIDS[string.byte(' ')] = true
|
||||||
INVALID_CHARS[string.byte('\r')] = true
|
INVALIDS[string.byte('\t')] = true
|
||||||
|
INVALIDS[string.byte('\n')] = true
|
||||||
|
INVALIDS[string.byte('\r')] = true
|
||||||
|
|
||||||
local NR_BYTE = string.byte('\n')
|
local NR_BYTE = string.byte('\n')
|
||||||
|
|
||||||
local PAIR_CHARS = {}
|
local PAIRS = {}
|
||||||
PAIR_CHARS[string.byte('[')] = string.byte(']')
|
PAIRS[string.byte('<')] = string.byte('>')
|
||||||
PAIR_CHARS[string.byte('(')] = string.byte(')')
|
PAIRS[string.byte('[')] = string.byte(']')
|
||||||
PAIR_CHARS[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
|
---Return if specified text has prefix or not
|
||||||
---@param text string
|
---@param text string
|
||||||
@ -38,6 +43,17 @@ str.has_prefix = function(text, prefix)
|
|||||||
return true
|
return true
|
||||||
end
|
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
|
---Remove suffix
|
||||||
---@param text string
|
---@param text string
|
||||||
---@param suffix string
|
---@param suffix string
|
||||||
@ -101,23 +117,47 @@ end
|
|||||||
|
|
||||||
---get_word
|
---get_word
|
||||||
---@param text string
|
---@param text string
|
||||||
|
---@param stop_char number
|
||||||
|
---@param min_length number
|
||||||
---@return string
|
---@return string
|
||||||
str.get_word = function(text, stop_char)
|
str.get_word = function(text, stop_char, min_length)
|
||||||
local valids = {}
|
min_length = min_length or 0
|
||||||
local has_valid = false
|
|
||||||
for idx = 1, #text do
|
local has_alnum = false
|
||||||
local c = string.byte(text, idx)
|
local stack = {}
|
||||||
local invalid = INVALID_CHARS[c] and not (valids[c] and stop_char ~= c)
|
local word = {}
|
||||||
if has_valid and invalid then
|
local add = function(c)
|
||||||
return string.sub(text, 1, idx - 1)
|
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
|
end
|
||||||
valids[c] = true
|
|
||||||
if PAIR_CHARS[c] then
|
|
||||||
valids[PAIR_CHARS[c]] = true
|
|
||||||
end
|
|
||||||
has_valid = has_valid or not invalid
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
---Oneline
|
---Oneline
|
||||||
|
@ -7,6 +7,9 @@ describe('utils.str', function()
|
|||||||
assert.are.equal(str.get_word('print()'), 'print')
|
assert.are.equal(str.get_word('print()'), 'print')
|
||||||
assert.are.equal(str.get_word('["cmp#confirm"]'), '["cmp#confirm"]')
|
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":', 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)
|
end)
|
||||||
|
|
||||||
it('strikethrough', function()
|
it('strikethrough', function()
|
||||||
|
@ -18,6 +18,7 @@ local api = require('cmp.utils.api')
|
|||||||
---@field public swin2 number|nil
|
---@field public swin2 number|nil
|
||||||
---@field public style cmp.WindowStyle
|
---@field public style cmp.WindowStyle
|
||||||
---@field public opt table<string, any>
|
---@field public opt table<string, any>
|
||||||
|
---@field public buffer_opt table<string, any>
|
||||||
---@field public cache cmp.Cache
|
---@field public cache cmp.Cache
|
||||||
local window = {}
|
local window = {}
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ window.new = function()
|
|||||||
self.style = {}
|
self.style = {}
|
||||||
self.cache = cache.new()
|
self.cache = cache.new()
|
||||||
self.opt = {}
|
self.opt = {}
|
||||||
|
self.buffer_opt = {}
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -54,6 +56,26 @@ window.option = function(self, key, value)
|
|||||||
end
|
end
|
||||||
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.
|
---Set style.
|
||||||
---@param style cmp.WindowStyle
|
---@param style cmp.WindowStyle
|
||||||
window.set_style = function(self, style)
|
window.set_style = function(self, style)
|
||||||
@ -70,7 +92,13 @@ end
|
|||||||
---Return buffer id.
|
---Return buffer id.
|
||||||
---@return number
|
---@return number
|
||||||
window.get_buffer = function(self)
|
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
|
end
|
||||||
|
|
||||||
---Open window
|
---Open window
|
||||||
@ -89,7 +117,7 @@ window.open = function(self, style)
|
|||||||
else
|
else
|
||||||
local s = misc.copy(self.style)
|
local s = misc.copy(self.style)
|
||||||
s.noautocmd = true
|
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
|
for k, v in pairs(self.opt) do
|
||||||
vim.api.nvim_win_set_option(self.win, k, v)
|
vim.api.nvim_win_set_option(self.win, k, v)
|
||||||
end
|
end
|
||||||
@ -148,7 +176,7 @@ window.update = function(self)
|
|||||||
-- In cmdline, vim does not redraw automatically.
|
-- In cmdline, vim does not redraw automatically.
|
||||||
if api.is_cmdline_mode() then
|
if api.is_cmdline_mode() then
|
||||||
vim.api.nvim_win_call(self.win, function()
|
vim.api.nvim_win_call(self.win, function()
|
||||||
vim.cmd([[redraw]])
|
misc.redraw()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -251,9 +279,14 @@ window.get_content_height = function(self)
|
|||||||
vim.api.nvim_buf_get_changedtick(self:get_buffer()),
|
vim.api.nvim_buf_get_changedtick(self:get_buffer()),
|
||||||
}, function()
|
}, function()
|
||||||
local height = 0
|
local height = 0
|
||||||
for _, text in ipairs(vim.api.nvim_buf_get_lines(self:get_buffer(), 0, -1, false)) do
|
local buf = self:get_buffer()
|
||||||
height = height + math.ceil(math.max(1, vim.str_utfindex(text)) / self.style.width)
|
-- The result of vim.fn.strdisplaywidth depends on the buffer it was called
|
||||||
end
|
-- 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
|
return height
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,7 @@ local event = require('cmp.utils.event')
|
|||||||
local keymap = require('cmp.utils.keymap')
|
local keymap = require('cmp.utils.keymap')
|
||||||
local docs_view = require('cmp.view.docs_view')
|
local docs_view = require('cmp.view.docs_view')
|
||||||
local custom_entries_view = require('cmp.view.custom_entries_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 native_entries_view = require('cmp.view.native_entries_view')
|
||||||
local ghost_text_view = require('cmp.view.ghost_text_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 resolve_dedup cmp.AsyncDedup
|
||||||
---@field private native_entries_view cmp.NativeEntriesView
|
---@field private native_entries_view cmp.NativeEntriesView
|
||||||
---@field private custom_entries_view cmp.CustomEntriesView
|
---@field private custom_entries_view cmp.CustomEntriesView
|
||||||
|
---@field private wildmenu_entries_view cmp.CustomEntriesView
|
||||||
---@field private change_dedup cmp.AsyncDedup
|
---@field private change_dedup cmp.AsyncDedup
|
||||||
---@field private docs_view cmp.DocsView
|
---@field private docs_view cmp.DocsView
|
||||||
---@field private ghost_text_view cmp.GhostTextView
|
---@field private ghost_text_view cmp.GhostTextView
|
||||||
@ -23,6 +25,7 @@ view.new = function()
|
|||||||
self.resolve_dedup = async.dedup()
|
self.resolve_dedup = async.dedup()
|
||||||
self.custom_entries_view = custom_entries_view.new()
|
self.custom_entries_view = custom_entries_view.new()
|
||||||
self.native_entries_view = native_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.docs_view = docs_view.new()
|
||||||
self.ghost_text_view = ghost_text_view.new()
|
self.ghost_text_view = ghost_text_view.new()
|
||||||
self.event = event.new()
|
self.event = event.new()
|
||||||
@ -47,7 +50,7 @@ end
|
|||||||
view.open = function(self, ctx, sources)
|
view.open = function(self, ctx, sources)
|
||||||
local source_group_map = {}
|
local source_group_map = {}
|
||||||
for _, s in ipairs(sources) do
|
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
|
if not source_group_map[group_index] then
|
||||||
source_group_map[group_index] = {}
|
source_group_map[group_index] = {}
|
||||||
end
|
end
|
||||||
@ -77,10 +80,10 @@ view.open = function(self, ctx, sources)
|
|||||||
-- create filtered entries.
|
-- create filtered entries.
|
||||||
local offset = ctx.cursor.col
|
local offset = ctx.cursor.col
|
||||||
for i, s in ipairs(source_group) do
|
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
|
if not has_triggered_by_symbol_source or s.is_triggered_by_symbol then
|
||||||
-- source order priority bonus.
|
-- 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
|
for _, e in ipairs(s:get_entries(ctx)) do
|
||||||
e.score = e.score + priority
|
e.score = e.score + priority
|
||||||
@ -109,7 +112,7 @@ view.open = function(self, ctx, sources)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- close.
|
-- complete_done.
|
||||||
if #entries == 0 then
|
if #entries == 0 then
|
||||||
self:close()
|
self:close()
|
||||||
end
|
end
|
||||||
@ -117,6 +120,11 @@ end
|
|||||||
|
|
||||||
---Close menu
|
---Close menu
|
||||||
view.close = function(self)
|
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:_get_entries_view():close()
|
||||||
self.docs_view:close()
|
self.docs_view:close()
|
||||||
self.ghost_text_view:hide()
|
self.ghost_text_view:hide()
|
||||||
@ -153,6 +161,17 @@ view.select_prev_item = function(self, option)
|
|||||||
self:_get_entries_view():select_prev_item(option)
|
self:_get_entries_view():select_prev_item(option)
|
||||||
end
|
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
|
---Get first entry
|
||||||
---@param self cmp.Entry|nil
|
---@param self cmp.Entry|nil
|
||||||
view.get_first_entry = function(self)
|
view.get_first_entry = function(self)
|
||||||
@ -174,54 +193,51 @@ end
|
|||||||
---Return current configured entries_view
|
---Return current configured entries_view
|
||||||
---@return cmp.CustomEntriesView|cmp.NativeEntriesView
|
---@return cmp.CustomEntriesView|cmp.NativeEntriesView
|
||||||
view._get_entries_view = function(self)
|
view._get_entries_view = function(self)
|
||||||
local c = config.get()
|
|
||||||
self.native_entries_view.event:clear()
|
self.native_entries_view.event:clear()
|
||||||
self.custom_entries_view.event:clear()
|
self.custom_entries_view.event:clear()
|
||||||
|
self.wildmenu_entries_view.event:clear()
|
||||||
|
|
||||||
if c.experimental.native_menu then
|
local c = config.get()
|
||||||
self.native_entries_view.event:on('change', function()
|
local v = self.custom_entries_view
|
||||||
self:on_entry_change()
|
if (c.view and c.view.entries and (c.view.entries.name or c.view.entries)) == 'wildmenu' then
|
||||||
end)
|
v = self.wildmenu_entries_view
|
||||||
return self.native_entries_view
|
elseif (c.view and c.view.entries and (c.view.entries.name or c.view.entries)) == 'native' then
|
||||||
else
|
v = self.native_entries_view
|
||||||
self.custom_entries_view.event:on('change', function()
|
|
||||||
self:on_entry_change()
|
|
||||||
end)
|
|
||||||
return self.custom_entries_view
|
|
||||||
end
|
end
|
||||||
|
v.event:on('change', function()
|
||||||
|
self:on_entry_change()
|
||||||
|
end)
|
||||||
|
return v
|
||||||
end
|
end
|
||||||
|
|
||||||
---On entry change
|
---On entry change
|
||||||
view.on_entry_change = async.throttle(
|
view.on_entry_change = async.throttle(function(self)
|
||||||
vim.schedule_wrap(function(self)
|
if not self:visible() then
|
||||||
if not self:visible() then
|
return
|
||||||
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
|
end
|
||||||
local e = self:get_selected_entry()
|
e:resolve(vim.schedule_wrap(self.resolve_dedup(function()
|
||||||
if e then
|
if not self:visible() then
|
||||||
for _, c in ipairs(config.get().confirmation.get_commit_characters(e:get_commit_characters())) do
|
return
|
||||||
keymap.listen('i', c, function(...)
|
|
||||||
self.event:emit('keymap', ...)
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
e:resolve(vim.schedule_wrap(self.resolve_dedup(function()
|
self.docs_view:open(e, self:_get_entries_view():info())
|
||||||
if not self:visible() then
|
end)))
|
||||||
return
|
else
|
||||||
end
|
self.docs_view:close()
|
||||||
self.docs_view:open(e, self:_get_entries_view():info())
|
end
|
||||||
end)))
|
|
||||||
else
|
|
||||||
self.docs_view:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
e = e or self:get_first_entry()
|
e = e or self:get_first_entry()
|
||||||
if e then
|
if e then
|
||||||
self.ghost_text_view:show(e)
|
self.ghost_text_view:show(e)
|
||||||
else
|
else
|
||||||
self.ghost_text_view:hide()
|
self.ghost_text_view:hide()
|
||||||
end
|
end
|
||||||
end),
|
end, 20)
|
||||||
20
|
|
||||||
)
|
|
||||||
|
|
||||||
return view
|
return view
|
||||||
|
@ -5,6 +5,7 @@ vim.g.loaded_cmp = true
|
|||||||
|
|
||||||
local api = require "cmp.utils.api"
|
local api = require "cmp.utils.api"
|
||||||
local misc = require('cmp.utils.misc')
|
local misc = require('cmp.utils.misc')
|
||||||
|
local types = require('cmp.types')
|
||||||
local config = require('cmp.config')
|
local config = require('cmp.config')
|
||||||
local highlight = require('cmp.utils.highlight')
|
local highlight = require('cmp.utils.highlight')
|
||||||
|
|
||||||
@ -20,11 +21,12 @@ vim.cmd [[
|
|||||||
autocmd CompleteDone * lua require'cmp.utils.autocmd'.emit('CompleteDone')
|
autocmd CompleteDone * lua require'cmp.utils.autocmd'.emit('CompleteDone')
|
||||||
autocmd ColorScheme * call v:lua.cmp.plugin.colorscheme()
|
autocmd ColorScheme * call v:lua.cmp.plugin.colorscheme()
|
||||||
autocmd CmdlineEnter * call v:lua.cmp.plugin.cmdline.enter()
|
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
|
augroup END
|
||||||
]]
|
]]
|
||||||
|
|
||||||
misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'enter' }, function()
|
misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'enter' }, function()
|
||||||
if config.get().experimental.native_menu then
|
if config.is_native_menu() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if vim.fn.expand('<afile>')~= '=' then
|
if vim.fn.expand('<afile>')~= '=' then
|
||||||
@ -78,6 +80,11 @@ misc.set(_G, { 'cmp', 'plugin', 'colorscheme' }, function()
|
|||||||
guibg = 'NONE',
|
guibg = 'NONE',
|
||||||
ctermbg = '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', {
|
highlight.inherit('CmpItemMenuDefault', 'Pmenu', {
|
||||||
guibg = 'NONE',
|
guibg = 'NONE',
|
||||||
ctermbg = 'NONE',
|
ctermbg = 'NONE',
|
||||||
@ -86,32 +93,40 @@ end)
|
|||||||
_G.cmp.plugin.colorscheme()
|
_G.cmp.plugin.colorscheme()
|
||||||
|
|
||||||
if vim.fn.hlexists('CmpItemAbbr') ~= 1 then
|
if vim.fn.hlexists('CmpItemAbbr') ~= 1 then
|
||||||
vim.cmd [[highlight! default link CmpItemAbbr CmpItemAbbrDefault]]
|
vim.cmd [[highlight default link CmpItemAbbr CmpItemAbbrDefault]]
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.fn.hlexists('CmpItemAbbrDeprecated') ~= 1 then
|
if vim.fn.hlexists('CmpItemAbbrDeprecated') ~= 1 then
|
||||||
vim.cmd [[highlight! default link CmpItemAbbrDeprecated CmpItemAbbrDeprecatedDefault]]
|
vim.cmd [[highlight default link CmpItemAbbrDeprecated CmpItemAbbrDeprecatedDefault]]
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.fn.hlexists('CmpItemAbbrMatch') ~= 1 then
|
if vim.fn.hlexists('CmpItemAbbrMatch') ~= 1 then
|
||||||
vim.cmd [[highlight! default link CmpItemAbbrMatch CmpItemAbbrMatchDefault]]
|
vim.cmd [[highlight default link CmpItemAbbrMatch CmpItemAbbrMatchDefault]]
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.fn.hlexists('CmpItemAbbrMatchFuzzy') ~= 1 then
|
if vim.fn.hlexists('CmpItemAbbrMatchFuzzy') ~= 1 then
|
||||||
vim.cmd [[highlight! default link CmpItemAbbrMatchFuzzy CmpItemAbbrMatchFuzzyDefault]]
|
vim.cmd [[highlight default link CmpItemAbbrMatchFuzzy CmpItemAbbrMatchFuzzyDefault]]
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.fn.hlexists('CmpItemKind') ~= 1 then
|
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
|
end
|
||||||
|
|
||||||
if vim.fn.hlexists('CmpItemMenu') ~= 1 then
|
if vim.fn.hlexists('CmpItemMenu') ~= 1 then
|
||||||
vim.cmd [[highlight! default link CmpItemMenu CmpItemMenuDefault]]
|
vim.cmd [[highlight default link CmpItemMenu CmpItemMenuDefault]]
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.cmd [[command! CmpStatus lua require('cmp').status()]]
|
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
|
if vim.on_key then
|
||||||
vim.on_key(function(keys)
|
vim.on_key(function(keys)
|
||||||
|
Loading…
Reference in New Issue
Block a user