diff --git a/README.md b/README.md index 633f388ac..34c827277 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,14 @@ Readme! 1. There is a Github issue that documents [breaking changes](https://github.com/hrsh7th/nvim-cmp/issues/231) for nvim-cmp. Subscribe to the issue to be notified of upcoming breaking changes. 2. This is my hobby project. You can support me via GitHub sponsors. -3. Bug reports are welcome, but I might not fix if you don't provide a minimal reproduction configuration and steps. -4. The nvim-cmp documents is [here](./doc/cmp.txt). -5. The nvim-cmp is designed for `customization`! It's not designed to `work out of the box`. - +3. Bug reports are welcome, but don't expect a fix unless you provide minimal configuration and steps to reproduce your issue. Concept ==================== - Full support for LSP completion related capabilities - Powerful customizability via Lua functions -- Smart handling of key mapping +- Smart handling of key mappings - No flicker @@ -29,7 +26,7 @@ Setup ### Recommended Configuration -This example configuration uses `vim-plug` as the plugin manager and `vim-vsnip` as snippet plugin. +This example configuration uses `vim-plug` as the plugin manager and `vim-vsnip` as a snippet plugin. ```lua call plug#begin(s:plug_dir) @@ -61,7 +58,7 @@ call plug#end() set completeopt=menu,menuone,noselect lua < with each lsp server you've enabled. require('lspconfig')[''].setup { @@ -135,12 +132,9 @@ EOF ### Where can I find more completion sources? -- See the [Wiki](https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources) -- See the [GitHub topic](https://github.com/topics/nvim-cmp). +Have a look at the [Wiki](https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources) and the `nvim-cmp` [GitHub topic](https://github.com/topics/nvim-cmp). ### Where can I find advanced configuration examples? -See the [Wiki](https://github.com/hrsh7th/nvim-cmp/wiki) - - +See the [Wiki](https://github.com/hrsh7th/nvim-cmp/wiki). diff --git a/doc/cmp.txt b/doc/cmp.txt index d0dcfcf51..04795746a 100644 --- a/doc/cmp.txt +++ b/doc/cmp.txt @@ -12,6 +12,7 @@ Function |cmp-function| Mapping |cmp-mapping| Command |cmp-command| Highlight |cmp-highlight| +FileType |cmp-filetype| Autocmd |cmp-autocmd| Config |cmp-config| Config Helper |cmp-config-helper| @@ -24,7 +25,7 @@ This is nvim-cmp's document. 1. This help file uses the type definition notation like `{lsp,cmp,vim}.*` - You can find it in `../lua/cmp/types/init.lua`. -2. Advanced configuration is described on the wiki. +2. Advanced configuration is described in the wiki. - https://github.com/hrsh7th/nvim-cmp/wiki ============================================================================== @@ -32,17 +33,17 @@ Concept *cmp-concept* - Full support for LSP completion related capabilities - Powerful customization abilities via Lua functions -- Smart handling of key mapping +- Smart handling of key mappings - No flicker ============================================================================== Usage *cmp-usage* -Recommended configuration is written below. +A recommended configuration can be found below. NOTE: - 1. You must provide `snippet.expand` function. - 2. `cmp.setup.cmdline` won't work if you use `native` completion menu. - 3. You can disable the `default` options via specifying `cmp.config.disable` value. + 1. You must provide a `snippet.expand` function. + 2. `cmp.setup.cmdline` won't work if you use the `native` completion menu. + 3. You can disable the `default` options by specifying `cmp.config.disable` value. > call plug#begin(s:plug_dir) Plug 'neovim/nvim-lspconfig' @@ -134,7 +135,7 @@ Recommended configuration is written below. ============================================================================== Function *cmp-function* -NOTE: `lua require('cmp').complete()` can be used to call these functions in mapping. +NOTE: `lua require('cmp').complete()` can be used to call these functions in a mapping. *cmp.setup* (config: cmp.ConfigSchema) Setup global configuration. See configuration options. @@ -146,44 +147,44 @@ NOTE: `lua require('cmp').complete()` can be used to call these functio Setup configuration for the current buffer. *cmp.setup.cmdline* (cmdtype: string, config: cmp.ConfigSchema) - Setup cmdline configuration for the specific type of cmd. - See |getcmdtype()| - NOTE: nvim-cmp does not support the `=` cmd type. + Setup cmdline configuration for the specific type of command. + See |getcmdtype()|. + NOTE: nvim-cmp does not support the `=` command type. *cmp.visible* () - Return boolean showing whether the completion menu is visible or not. + Return a boolean showing whether the completion menu is visible or not. *cmp.get_entries* () Return all current entries. *cmp.get_selected_entry* () - Return current selected entry (contains preselected). + Return currently selected entry (including preselected). *cmp.get_active_entry* () - Return current selected entry (without preselected). + Return currently selected entry (excluding preselected). *cmp.close* () Close the completion menu. *cmp.abort* () - Closes the completion menu and restore the current line to the state when it was started current completion. + Closes the completion menu and restore the current line to the state before the current completion was started. *cmp.select_next_item* (option: { behavior = cmp.SelectBehavior }) - Select next item. + Select the next item. *cmp.select_prev_item* (option: { behavior = cmp.SelectBehavior })* - Select previous item. + Select the previous item. *cmp.open_docs_preview* Opens the docs window (if visible) in a preview window. *cmp.scroll_docs* (delta: number) - Scroll docs if visible. + Scroll the documentation window if visible. *cmp.complete* (option: { reason = cmp.ContextReason, config = cmp.ConfigSchema }) Invoke completion. - The following configurations defines the key mapping to show completion only for vsnip snippets. + The following configuration defines a key mapping to show completion only for vsnip snippets. > cmp.setup { mapping = { @@ -202,7 +203,7 @@ NOTE: `lua require('cmp').complete()` can be used to call these functio NOTE: `config` in that case means a temporary setting, but `config.mapping` remains permanent. *cmp.complete_common_string* () - Complete common string (reminds shell completion behavior). + Complete common string (similar to shell completion behavior). > cmp.setup { mapping = { @@ -216,27 +217,31 @@ NOTE: `lua require('cmp').complete()` can be used to call these functio } < *cmp.confirm* (option: cmp.ConfirmOption, callback: function) - Accepts current selected completion item. - If you didn't select any item and `{ select = true }` is specified for - this, nvim-cmp would automatically select the first item. + Accepts the currently selected completion item. + If you didn't select any item and the option table contains `select = true`, + nvim-cmp will automatically select the first item. -*cmp.event:on* ('%EVENT_NAME%, callback) +*cmp.event:on* (%EVENT_NAME%, callback) Subscribe to nvim-cmp's event. Events are listed below. - `complete_done`: emit after current completion is done. - `confirm_done`: emit after confirmation is done. + - `menu_opened`: emit after opening a new completion menu. Called with a table holding a key + named `window`, pointing to the completion menu implementation. + - `menu_closed`: emit after completion menu is closed. Called with a table holding a key + named `window`, pointing to the completion menu implementation. ============================================================================== Mapping *cmp-mapping* Nvim-cmp's mapping mechanism is complex but flexible and user-friendly. -You can specify the mapping function that receives the `fallback` function as argument. +You can specify a mapping function that receives a `fallback` function as an argument. The `fallback` function can be used to call an existing mapping. -For example, typical pair-wise plugin automatically defines the mappings for `` and `(`. -Nvim-cmp might overwrite it via specified mapping. -But you can use existing mapping via invoking the `fallback` function. +For example, typical pair-wise plugins automatically define mappings for `` and `(`. +Nvim-cmp will overwrite it if you provide a mapping. To call the existing mapping, +you would need to invoke the `fallback` function. > cmp.setup { mapping = { @@ -244,7 +249,7 @@ But you can use existing mapping via invoking the `fallback` function. if cmp.visible() then cmp.confirm() else - fallback() -- If you use vim-endwise, this fallback will behave as vim-endwise. + fallback() -- If you use vim-endwise, this fallback will behave the same as vim-endwise. end end } @@ -263,7 +268,7 @@ But you can use existing mapping via invoking the `fallback` function. } < -And you can specify the mapping modes. +It is possible to specify the modes the mapping should be active in (`i` = insert mode, `c` = command mode, `s` = select mode): > cmp.setup { mapping = { @@ -271,7 +276,7 @@ And you can specify the mapping modes. } } < -And you can specify different mappings for different modes using the same key. +You can also specify different mappings for different modes by passing a table: > cmp.setup { mapping = { @@ -282,7 +287,7 @@ And you can specify different mappings for different modes using the same key. } } < -You can also use built-in mapping helpers. +There are also builtin mapping helper functions you can use: *cmp.mapping.close* () Same as |cmp.close|. @@ -314,38 +319,42 @@ You can also use built-in mapping helpers. Built-in mapping helpers are only available as a configuration option. If you want to call nvim-cmp features directly, please use |cmp-function| instead. + + ============================================================================== Command *cmp-command* *CmpStatus* Describes statuses and states of sources. - Sometimes `unknown` source will be printed but it isn't problem. - For that reason `cmp-nvim-lsp` registered on the InsertEnter autocmd will - have `unknown` status. + Sometimes `unknown` will be printed - this is expected. + For example, `cmp-nvim-lsp` registers itself on InsertEnter autocommand + so the status will be shown as `unknown` when running the command. + + ============================================================================== Highlight *cmp-highlight* *CmpItemAbbr* - Highlights unmatched characters of each completion field. + Highlight group for unmatched characters of each completion field. *CmpItemAbbrDeprecated* - Highlights unmatched characters of each deprecated completion field. + Highlight group for unmatched characters of each deprecated completion field. *CmpItemAbbrMatch* - Highlights matched characters of each completion field. Matched characters + Highlight group for matched characters of each completion field. Matched characters must form a substring of a field which share a starting position. *CmpItemAbbrMatchFuzzy* - Highlights fuzzy-matched characters of each completion field. + Highlight group for fuzzy-matched characters of each completion field. *CmpItemKind* - Highlights kind of the field. + Highlight group for the kind of the field. NOTE: `kind` is a symbol after each completion option. *CmpItemKind%KIND_NAME%* - Highlights kind of the field for specific `lsp.CompletionItemKind`. + Highlight group for the kind of the field for a specific `lsp.CompletionItemKind`. If you only want to overwrite the `method` kind's highlight group, you can do this: > highlight CmpItemKindMethod guibg=NONE guifg=Orange @@ -353,6 +362,15 @@ NOTE: `kind` is a symbol after each completion option. *CmpItemMenu* The menu field's highlight group. +============================================================================== +FileType *cmp-filetype* + +*cmp_menu* + The completion menu buffer's filetype. + +*cmp_docs* + The documentation window buffer's filetype. + ============================================================================== Autocmd *cmp-autocmd* @@ -372,14 +390,33 @@ enabled~ `boolean | fun(): boolean` Toggles the plugin on and off. + *cmp-config.performance.debounce* +performance.debounce~ + `number` + Sets debounce time + This is the interval used to group up completions from different sources + for filtering and displaying. + + *cmp-config.performance.throttle* +performance.throttle~ + `number` + Sets throttle time + This is used to delay filtering and displaying completions. + + *cmp-config.performance.fetching_timeout* + performance.fetching_timeout~ + `number` + Sets the timeout of candidate fetching process. + The nvim-cmp will wait to display the most prioritized source. + *cmp-config.preselect* preselect~ `cmp.PreselectMode` 1. `cmp.PreselectMode.Item` - nvim-cmp will pre-select the item that the source specified. + nvim-cmp will preselect the item that the source specified. 2. `cmp.PreselectMode.None` - nvim-cmp wouldn't pre-select any items. + nvim-cmp will not preselect any items. *cmp-config.mapping* mapping~ @@ -389,7 +426,8 @@ mapping~ *cmp-config.snippet.expand* snippet.expand~ `fun(option: cmp.SnippetExpansionParams)` - The snippet expansion function. That's how cmp interacts with snippet engine. + The snippet expansion function. That's how nvim-cmp interacts with a + particular snippet engine. *cmp-config.completion.keyword_length* completion.keyword_length~ @@ -404,33 +442,33 @@ completion.keyword_pattern~ *cmp-config.completion.autocomplete* completion.autocomplete~ `cmp.TriggerEvent[] | false` - The event to trigger autocompletion. If false specified, than completion is - only invoked manually. + The event to trigger autocompletion. If set to `false`, then completion is + only invoked manually (e.g. by calling `cmp.complete`). *cmp-config.completion.completeopt* completion.completeopt~ `string` - The vim's completeopt-like setting. See 'completeopt'. - Besically, you don't need to change this. + Like vim's completeopt setting. See 'completeopt'. + In general, you don't need to change this. *cmp-config.confirmation.get_commit_characters* confirmation.get_commit_characters~ `fun(commit_characters:string[]):string[]` - You can append or exclude commitCharacters via this configuration option function. - The commitCharacters is defined by LSP spec. + You can append or exclude commitCharacters via this configuration option + function. The commitCharacters are defined by the LSP spec. *cmp-config.formatting.fields* formatting.fields~ `cmp.ItemField[]` - The array of completion fields to specify their order. + An array of completion fields to specify their order. *cmp-config.formatting.format* formatting.format~ `fun(entry: cmp.Entry, vim_item: vim.CompletedItem): vim.CompletedItem` - The function used to customize the completion menu appearance. See - |complete-items|. This value can also be used to modify `dup` property. - NOTE: The `vim.CompletedItem` can have special properties `abbr_hl_group`, - `kind_hl_group` and `menu_hl_group`. + The function used to customize the appearance of the completion menu. See + |complete-items|. This value can also be used to modify the `dup` property. + NOTE: The `vim.CompletedItem` can contain the special properties + `abbr_hl_group`, `kind_hl_group` and `menu_hl_group`. *cmp-config.matching.disallow_fuzzy_matching* matching.disallow_fuzzy_matching~ @@ -466,50 +504,50 @@ sorting.comparators~ sources~ `cmp.SourceConfig[]` List of the sources and their configurations to use. - Order of the sources matters for sorting order. + The order of the sources determines their order in the completion results. *cmp-config.sources[n].name* sources[n].name~ `string` - The source name. + The name of the source. *cmp-config.sources[n].option* sources[n].option~ `table` - The specific options defined by the source itself. + Any specific options defined by the source itself. *cmp-config.sources[n].keyword_length* sources[n].keyword_length~ `number` - The source specific keyword length to trigger auto completion. + The source-specific keyword length to trigger auto completion. *cmp-config.sources[n].keyword_pattern* sources[n].keyword_pattern~ - `number` - The source specific keyword pattern. + `string` + The source-specific keyword pattern. *cmp-config.sources[n].trigger_characters* sources[n].trigger_characters~ `string[]` - The source specific keyword pattern. + A source-specific keyword pattern. *cmp-config.sources[n].priority* sources[n].priority~ `number` - The source specific priority value. + The source-specific priority value. *cmp-config.sources[n].max_item_count* sources[n].max_item_count~ `number` - The source specific item count. + The source-specific item count. *cmp-config.sources[n].group_index* sources[n].group_index~ `number` The source group index. - For instance, you can specify the `buffer`'s source `group_index` to bigger number - if you don't want to see the buffer source items when nvim-lsp source is available. + For instance, you can set the `buffer`'s source `group_index` to a larger number + if you don't want to see `buffer` source items while `nvim-lsp` source is available: > cmp.setup { sources = { @@ -518,7 +556,7 @@ sources[n].group_index~ } } < - You can specify this via the built-in configuration helper like this. + You can also achieve this by using the built-in configuration helper like this: > cmp.setup { sources = cmp.config.sources({ @@ -528,10 +566,35 @@ sources[n].group_index~ }) } < + + *cmp-config.sources[n].entry_filter* +sources[n].entry_filter~ + `function` + A source-specific entry filter, with the following function signature: +> + function(entry: cmp.Entry, ctx: cmp.Context): boolean +< + + Returning `true` will keep the entry, while returning `false` will remove it. + + This can be used to hide certain entries from a given source. For instance, you + could hide all entries with kind `Text` from the `nvim_lsp` filter using the + following source definition: +> + { + name = 'nvim_lsp', + entry_filter = function(entry, ctx) + return require('cmp.types').lsp.CompletionItemKind[entry:get_kind()] ~= 'Text' + end + } +< + Using the `ctx` parameter, you can further customize the behaviour of the + source. + *cmp-config.view* view~ `{ entries: cmp.EntriesConfig|string }` - Specify the view class to customize appearance. + The view class used to customize nvim-cmp's appearance. Currently available configuration options are: *cmp-config.window.{completion,documentation}.border* @@ -552,6 +615,16 @@ window.{completion,documentation}.zindex~ The completion window's zindex. See |nvim_open_win|. + *cmp-config.window.completion.col_offset* +window.completion.col_offset~ + `number` + Offsets the completion window relative to the cursor. + + *cmp-config.window.completion.side_padding* +window.completion.side_padding~ + `number` + The ammount of padding to add on the completion window's sides + *cmp-config.window.documentation.max_width* window.documentation.max_width~ `number` @@ -593,11 +666,11 @@ cmp.config.context~ *cmp.config.context.in_treesitter_capture* (capture) You can specify the treesitter capture name. - If you don't use `nvim-treesitter`, this helper doesn't work correctly. + If you don't use the `nvim-treesitter` plugin, this helper will not work correctly. cmp.config.mapping~ - See |cmp-mapping| + See |cmp-mapping|. cmp.config.sources~ @@ -617,7 +690,7 @@ cmp.config.sources~ cmp.config.window~ *cmp.config.window.bordered* (option) - Make window `bordered`. + Make the completion window `bordered`. The option is described in `cmp.ConfigSchema`. > cmp.setup { @@ -630,46 +703,46 @@ cmp.config.window~ ============================================================================== Develop *cmp-develop* -Create custom source~ +Creating a custom source~ NOTE: 1. The `complete` method is required. Others can be omitted. - 2. The `callback` argument must always be called. + 2. The `callback` function must always be called. 3. You can use only `require('cmp')` in custom source. - 4. If LSP spec was changed, nvim-cmp would follow it without any announcement. + 4. If the LSP spec was changed, nvim-cmp may implement it without any announcement (potentially introducing breaking changes). 5. You should read ./lua/cmp/types and https://microsoft.github.io/language-server-protocol/specifications/specification-current. - 6. Please add `nvim-cmp` topic for github repo. - -You can create custom source like the following example. + 6. Please add your source to the list of sources in the Wiki (https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources) + and if you publish it on GitHub, add the `nvim-cmp` topic so users can find it more easily. +Here is an example on how to create a custom source: > local source = {} - ---Return this source is available in current context or not. (Optional) + ---Return whether this source is available in the current context or not (optional). ---@return boolean function source:is_available() return true end - ---Return the debug name of this source. (Optional) + ---Return the debug name of this source (optional). ---@return string function source:get_debug_name() return 'debug name' end - ---Return keyword pattern for triggering completion. (Optional) - ---If this is ommited, nvim-cmp will use default keyword pattern. See |cmp-config.completion.keyword_pattern| + ---Return the keyword pattern for triggering completion (optional). + ---If this is ommited, nvim-cmp will use a default keyword pattern. See |cmp-config.completion.keyword_pattern|. ---@return string function source:get_keyword_pattern() return [[\k\+]] end - ---Return trigger characters for triggering completion. (Optional) + ---Return trigger characters for triggering completion (optional). function source:get_trigger_characters() return { '.' } end - ---Invoke completion. (Required) + ---Invoke completion (required). ---@param params cmp.SourceCompletionApiParams ---@param callback fun(response: lsp.CompletionResponse|nil) function source:complete(params, callback) @@ -689,21 +762,22 @@ You can create custom source like the following example. }) end - ---Resolve completion item. (Optional) + ---Resolve completion item (optional). This is called right before the completion is about to be displayed. + ---Useful for setting the text shown in the documentation window (`completion_item.documentation`). ---@param completion_item lsp.CompletionItem ---@param callback fun(completion_item: lsp.CompletionItem|nil) function source:resolve(completion_item, callback) callback(completion_item) end - ---Execute command after item was accepted. + ---Executed after the item was selected. ---@param completion_item lsp.CompletionItem ---@param callback fun(completion_item: lsp.CompletionItem|nil) function source:execute(completion_item, callback) callback(completion_item) end - ---Register custom source to nvim-cmp. + ---Register your source to nvim-cmp. require('cmp').register_source('month', source.new()) < ============================================================================== @@ -712,20 +786,19 @@ FAQ *cmp-faq* Why does cmp automatically select a particular item? ~ How to disable the preselect feature? ~ - Nvim-cmp respects LSP(Language Server Protocol) specification. + Nvim-cmp respects the LSP (Language Server Protocol) specification. The LSP spec defines the `preselect` feature for completion. - You can disable the `preselect` feature like the following. + You can disable the `preselect` feature like this: > cmp.setup { preselect = cmp.PreselectMode.None } < -Why nvim-cmp confirm item automatically?~ How to disable commitCharacters?~ - You can disable commitCharacters feature (that defined in LSP spec). + You can disable the commitCharacters feature (which is defined in LSP spec): > cmp.setup { confirmation = { @@ -738,9 +811,9 @@ How to disable commitCharacters?~ How to disable auto-completion?~ -How to use nvim-cmp as like omnifunc?~ +How to use nvim-cmp as omnifunc?~ - You can disable auto-completion like this. + You can disable auto-completion like this: > cmp.setup { ... @@ -750,15 +823,15 @@ How to use nvim-cmp as like omnifunc?~ ... } < - And you can invoke completion manually. + Then you will need to invoke completion manually. > inoremap lua require('cmp').complete() < -How to disable nvim-cmp on the specific buffer?~ -How to setup on the specific buffer?~ +How to disable nvim-cmp for a specific buffer?~ +How to setup nvim-cmp for a specific buffer?~ - You can setup buffer specific configuration like this. + You can setup buffer-specific configuration like this: > cmp.setup.filetype({ 'markdown', 'help' }, { sources = { @@ -768,9 +841,9 @@ How to setup on the specific buffer?~ }) < -How to disable documentation window?~ +How to disable the documentation window?~ - You can use the following config. + Simply use the following config: > cmp.setup.filetype({ 'markdown', 'help' }, { window = { @@ -779,17 +852,23 @@ How to disable documentation window?~ }) < +I'm using clangd. The menu items are mis-indented.~ + + It's caused by clangd. You can specify `--header-insertion-decorators` for + clangd's command-line arguments. See #999. + + How to integrate with copilot.vim?~ Copilot.vim and nvim-cmp both have a `key-mapping fallback` mechanism. Therefore, you should manage those plugins by yourself. - Fortunately, the copilot.vim has the feature that disables the fallback mechanism. + Fortunately, the copilot.vim has a feature that disables the fallback mechanism. > let g:copilot_no_tab_map = v:true imap (vimrc:copilot-dummy-map) copilot#Accept("\") < - You can manage copilot.vim's accept feature with nvim-cmp' key-mapping configuration. + You can manage copilot.vim's accept feature inside nvim-cmp's key-mapping function: > cmp.setup { mapping = { @@ -804,9 +883,9 @@ How to integrate with copilot.vim?~ < -How to customize menu appearance?~ +How to customize the menu appearance?~ - You can see nvim-cmp wiki (https://github.com/hrsh7th/nvim-cmp/wiki). + Have a look at the wiki (https://github.com/hrsh7th/nvim-cmp/wiki). ============================================================================== vim:tw=78:ts=2:et:ft=help:norl: diff --git a/lua/cmp/config.lua b/lua/cmp/config.lua index 42e9a43f7..ac0dbf1eb 100644 --- a/lua/cmp/config.lua +++ b/lua/cmp/config.lua @@ -14,7 +14,7 @@ config.cache = cache.new() ---@type cmp.ConfigSchema config.global = require('cmp.config.default')() ----@type table +---@type table config.buffers = {} ---@type table @@ -36,7 +36,7 @@ end ---Set configuration for buffer ---@param c cmp.ConfigSchema ----@param bufnr number|nil +---@param bufnr integer config.set_buffer = function(c, bufnr) local revision = (config.buffers[bufnr] or {}).revision or 1 config.buffers[bufnr] = c or {} @@ -74,7 +74,9 @@ end ---@return cmp.ConfigSchema config.get = function() local global_config = config.global - if config.onetime.sources then + + -- The config object already has `revision` key. + if #vim.tbl_keys(config.onetime) > 1 then local onetime_config = config.onetime return config.cache:ensure({ 'get', @@ -150,9 +152,6 @@ end ---Return the current menu is native or not. config.is_native_menu = function() local c = config.get() - if c.experimental and c.experimental.native_menu then - return true - end if c.view and c.view.entries then return c.view.entries == 'native' or c.view.entries.name == 'native' end @@ -164,6 +163,7 @@ end ---@return cmp.ConfigSchema config.normalize = function(c) -- make sure c is not 'nil' + ---@type any c = c == nil and {} or c -- Normalize mapping. diff --git a/lua/cmp/config/default.lua b/lua/cmp/config/default.lua index dce5168b9..009807995 100644 --- a/lua/cmp/config/default.lua +++ b/lua/cmp/config/default.lua @@ -14,6 +14,12 @@ return function() return not disabled end, + performance = { + debounce = 60, + throttle = 30, + fetching_timeout = 200, + }, + preselect = types.cmp.PreselectMode.Item, mapping = {}, @@ -85,6 +91,8 @@ return function() completion = { border = { '', '', '', '', '', '', '', '' }, winhighlight = 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None', + col_offset = 0, + side_padding = 1, }, documentation = { max_height = math.floor(WIDE_HEIGHT * (WIDE_HEIGHT / vim.o.lines)), diff --git a/lua/cmp/config/mapping.lua b/lua/cmp/config/mapping.lua index e0ccf60da..ccd78f611 100644 --- a/lua/cmp/config/mapping.lua +++ b/lua/cmp/config/mapping.lua @@ -3,6 +3,20 @@ local misc = require('cmp.utils.misc') local feedkeys = require('cmp.utils.feedkeys') local keymap = require('cmp.utils.keymap') +local function merge_keymaps(base, override) + local normalized_base = {} + for k, v in pairs(base) do + normalized_base[keymap.normalize(k)] = v + end + + local normalized_override = {} + for k, v in pairs(override) do + normalized_override[keymap.normalize(k)] = v + end + + return misc.merge(normalized_base, normalized_override) +end + local mapping = setmetatable({}, { __call = function(_, invoke, modes) if type(invoke) == 'function' then @@ -21,7 +35,7 @@ mapping.preset = {} ---Mapping preset insert-mode configuration. mapping.preset.insert = function(override) - return misc.merge(override or {}, { + return merge_keymaps(override or {}, { [''] = { i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }), }, @@ -45,7 +59,7 @@ end ---Mapping preset cmdline-mode configuration. mapping.preset.cmdline = function(override) - return misc.merge(override or {}, { + return merge_keymaps(override or {}, { [''] = { c = function() local cmp = require('cmp') diff --git a/lua/cmp/config/window.lua b/lua/cmp/config/window.lua index b6fec0fee..88133fc5c 100644 --- a/lua/cmp/config/window.lua +++ b/lua/cmp/config/window.lua @@ -6,6 +6,8 @@ window.bordered = function(opts) border = opts.border or 'rounded', winhighlight = opts.winhighlight or 'Normal:Normal,FloatBorder:Normal,CursorLine:Visual,Search:None', zindex = opts.zindex or 1001, + col_offset = opts.col_offset or 0, + side_padding = opts.side_padding or 1, } end diff --git a/lua/cmp/context.lua b/lua/cmp/context.lua index 6188259eb..0c25462cd 100644 --- a/lua/cmp/context.lua +++ b/lua/cmp/context.lua @@ -10,8 +10,8 @@ local api = require('cmp.utils.api') ---@field public prev_context cmp.Context ---@field public option cmp.ContextOption ---@field public filetype string ----@field public time number ----@field public bufnr number +---@field public time integer +---@field public bufnr integer ---@field public cursor vim.Position|lsp.Position ---@field public cursor_line string ---@field public cursor_after_line string @@ -31,8 +31,8 @@ context.empty = function() end ---Create new context ----@param prev_context cmp.Context ----@param option cmp.ContextOption +---@param prev_context? cmp.Context +---@param option? cmp.ContextOption ---@return cmp.Context context.new = function(prev_context, option) option = option or {} @@ -65,7 +65,7 @@ context.get_reason = function(self) end ---Get keyword pattern offset ----@return number|nil +---@return integer context.get_offset = function(self, keyword_pattern) return self.cache:ensure({ 'get_offset', keyword_pattern, self.cursor_before_line }, function() return pattern.offset(keyword_pattern .. '\\m$', self.cursor_before_line) or self.cursor.col diff --git a/lua/cmp/core.lua b/lua/cmp/core.lua index 08949a91c..423420d09 100644 --- a/lua/cmp/core.lua +++ b/lua/cmp/core.lua @@ -14,10 +14,6 @@ local types = require('cmp.types') local api = require('cmp.utils.api') local event = require('cmp.utils.event') -local SOURCE_TIMEOUT = 500 -local DEBOUNCE_TIME = 80 -local THROTTLE_TIME = 40 - ---@class cmp.Core ---@field public suspending boolean ---@field public view cmp.View @@ -36,9 +32,11 @@ core.new = function() self.view.event:on('keymap', function(...) self:on_keymap(...) end) - self.view.event:on('complete_done', function(evt) - self.event:emit('complete_done', evt) - end) + for _, event_name in ipairs({ 'complete_done', 'menu_opened', 'menu_closed' }) do + self.view.event:on(event_name, function(evt) + self.event:emit(event_name, evt) + end) + end return self end @@ -49,13 +47,13 @@ core.register_source = function(self, s) end ---Unregister source ----@param source_id string +---@param source_id integer core.unregister_source = function(self, source_id) self.sources[source_id] = nil end ---Get new context ----@param option cmp.ContextOption +---@param option? cmp.ContextOption ---@return cmp.Context core.get_context = function(self, option) local prev = self.context:clone() @@ -81,7 +79,7 @@ core.suspend = function(self) end ---Get sources that sorted by priority ----@param filter cmp.SourceStatus[]|fun(s: cmp.Source): boolean +---@param filter? cmp.SourceStatus[]|fun(s: cmp.Source): boolean ---@return cmp.Source[] core.get_sources = function(self, filter) local f = function(s) @@ -169,7 +167,7 @@ core.on_change = function(self, trigger_event) if vim.tbl_contains(config.get().completion.autocomplete or {}, trigger_event) then self:complete(ctx) else - self.filter.timeout = self.view:visible() and THROTTLE_TIME or 0 + self.filter.timeout = self.view:visible() and config.get().performance.throttle or 0 self:filter() end else @@ -241,7 +239,7 @@ core.complete_common_string = function(self) config.set_onetime({}) local cursor = api.get_cursor() - local offset = self.view:get_offset() + local offset = self.view:get_offset() or cursor[2] local common_string for _, e in ipairs(self.view:get_entries()) do local vim_item = e:get_vim_item(offset) @@ -279,7 +277,7 @@ core.complete = function(self, ctx) else if not self.view:get_active_entry() then self.filter.stop() - self.filter.timeout = DEBOUNCE_TIME + self.filter.timeout = config.get().performance.debounce self:filter() end end @@ -289,14 +287,14 @@ core.complete = function(self, ctx) end if not self.view:get_active_entry() then - self.filter.timeout = self.view:visible() and THROTTLE_TIME or 1 + self.filter.timeout = self.view:visible() and config.get().performance.throttle or 1 self:filter() end end ---Update completion menu core.filter = async.throttle(function(self) - self.filter.timeout = THROTTLE_TIME + self.filter.timeout = config.get().performance.throttle -- Check invalid condition. local ignore = false @@ -309,8 +307,8 @@ core.filter = async.throttle(function(self) local sources = {} for _, s in ipairs(self:get_sources({ source.SourceStatus.FETCHING, source.SourceStatus.COMPLETED })) do -- Reserve filter call for timeout. - if not s.incomplete and SOURCE_TIMEOUT > s:get_fetching_time() then - self.filter.timeout = SOURCE_TIMEOUT - s:get_fetching_time() + if not s.incomplete and config.get().performance.fetching_timeout > s:get_fetching_time() then + self.filter.timeout = config.get().performance.fetching_timeout - s:get_fetching_time() self:filter() if #sources == 0 then return @@ -335,7 +333,7 @@ core.filter = async.throttle(function(self) end) == 0 then config.set_onetime({}) end -end, THROTTLE_TIME) +end, config.get().performance.throttle) ---Confirm completion. ---@param e cmp.Entry @@ -371,6 +369,7 @@ core.confirm = function(self, e, option, callback) table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset())) feedkeys.call(table.concat(keys, ''), 'in') else + vim.cmd([[silent! undojoin]]) 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()), }) @@ -402,9 +401,11 @@ core.confirm = function(self, e, option, callback) if has_cursor_line_text_edit then return end + vim.cmd([[silent! undojoin]]) vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, 'utf-16') end) else + vim.cmd([[silent! undojoin]]) vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, 'utf-16') end end) diff --git a/lua/cmp/entry.lua b/lua/cmp/entry.lua index 83c53a209..6fa54600f 100644 --- a/lua/cmp/entry.lua +++ b/lua/cmp/entry.lua @@ -7,15 +7,15 @@ local types = require('cmp.types') local matcher = require('cmp.matcher') ---@class cmp.Entry ----@field public id number +---@field public id integer ---@field public cache cmp.Cache ---@field public match_cache cmp.Cache ----@field public score number +---@field public score integer ---@field public exact boolean ---@field public matches table ---@field public context cmp.Context ---@field public source cmp.Source ----@field public source_offset number +---@field public source_offset integer ---@field public source_insert_range lsp.Range ---@field public source_replace_range lsp.Range ---@field public completion_item lsp.CompletionItem @@ -52,12 +52,13 @@ entry.new = function(ctx, source, completion_item) end ---Make offset value ----@return number +---@return integer entry.get_offset = function(self) return self.cache:ensure({ 'get_offset', self.resolved_completion_item and 1 or 0 }, function() local offset = self.source_offset if misc.safe(self:get_completion_item().textEdit) then - local range = misc.safe(self:get_completion_item().textEdit.insert) or misc.safe(self:get_completion_item().textEdit.range) + local range = misc.safe(self:get_completion_item().textEdit.insert) or + misc.safe(self:get_completion_item().textEdit.range) if range then local c = misc.to_vimindex(self.context.cursor_line, range.start.character) for idx = c, self.source_offset do @@ -130,16 +131,19 @@ entry.get_word = function(self) end ---Get overwrite information ----@return number, number +---@return integer, integer entry.get_overwrite = function(self) return self.cache:ensure({ 'get_overwrite', self.resolved_completion_item and 1 or 0 }, function() if misc.safe(self:get_completion_item().textEdit) then - local r = misc.safe(self:get_completion_item().textEdit.insert) or misc.safe(self:get_completion_item().textEdit.range) - local s = misc.to_vimindex(self.context.cursor_line, r.start.character) - local e = misc.to_vimindex(self.context.cursor_line, r['end'].character) - local before = self.context.cursor.col - s - local after = e - self.context.cursor.col - return { before, after } + local r = misc.safe(self:get_completion_item().textEdit.insert) or + misc.safe(self:get_completion_item().textEdit.range) + if r then + local s = misc.to_vimindex(self.context.cursor_line, r.start.character) + local e = misc.to_vimindex(self.context.cursor_line, r['end'].character) + local before = self.context.cursor.col - s + local after = e - self.context.cursor.col + return { before, after } + end end return { 0, 0 } end) @@ -184,13 +188,14 @@ end ---Return the item is deprecated or not. ---@return boolean entry.is_deprecated = function(self) - return self:get_completion_item().deprecated or vim.tbl_contains(self:get_completion_item().tags or {}, types.lsp.CompletionItemTag.Deprecated) + return self:get_completion_item().deprecated or + vim.tbl_contains(self:get_completion_item().tags or {}, types.lsp.CompletionItemTag.Deprecated) end ---Return view information. ----@param suggest_offset number ----@param entries_buf number The buffer this entry will be rendered into. ----@return { abbr: { text: string, bytes: number, width: number, hl_group: string }, kind: { text: string, bytes: number, width: number, hl_group: string }, menu: { text: string, bytes: number, width: number, hl_group: string } } +---@param suggest_offset integer +---@param entries_buf integer The buffer this entry will be rendered into. +---@return { abbr: { text: string, bytes: integer, width: integer, hl_group: string }, kind: { text: string, bytes: integer, width: integer, hl_group: string }, menu: { text: string, bytes: integer, width: integer, hl_group: string } } entry.get_view = function(self, suggest_offset, entries_buf) local item = self:get_vim_item(suggest_offset) return self.cache:ensure({ 'get_view', self.resolved_completion_item and 1 or 0, entries_buf }, function() @@ -208,7 +213,8 @@ entry.get_view = function(self, suggest_offset, entries_buf) view.kind.text = item.kind or '' view.kind.bytes = #view.kind.text view.kind.width = vim.fn.strdisplaywidth(view.kind.text) - view.kind.hl_group = item.kind_hl_group or ('CmpItemKind' .. (types.lsp.CompletionItemKind[self:get_kind()] or '')) + view.kind.hl_group = item.kind_hl_group or ('CmpItemKind' .. (types.lsp.CompletionItemKind[self:get_kind()] or '') + ) view.menu = {} view.menu.text = item.menu or '' view.menu.bytes = #view.menu.text @@ -221,7 +227,7 @@ entry.get_view = function(self, suggest_offset, entries_buf) end ---Make vim.CompletedItem ----@param suggest_offset number +---@param suggest_offset integer ---@return vim.CompletedItem entry.get_vim_item = function(self, suggest_offset) return self.cache:ensure({ 'get_vim_item', suggest_offset, self.resolved_completion_item and 1 or 0 }, function() @@ -269,10 +275,13 @@ entry.get_vim_item = function(self, suggest_offset) end end + local cmp_opts = self:get_completion_item().cmp or {} + local vim_item = { word = word, abbr = abbr, - kind = types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1], + kind = cmp_opts.kind_text or types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1], + kind_hl_group = cmp_opts.kind_hl_group, menu = menu, dup = self:get_completion_item().dup or 1, } @@ -310,7 +319,8 @@ entry.get_insert_range = function(self) insert_range = { start = { line = self.context.cursor.row - 1, - character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), self.source_insert_range.start.character), + character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), + self.source_insert_range.start.character), }, ['end'] = self.source_insert_range['end'], } @@ -323,13 +333,18 @@ end entry.get_replace_range = function(self) return self.cache:ensure({ 'get_replace_range', self.resolved_completion_item and 1 or 0 }, function() local replace_range - if misc.safe(self:get_completion_item().textEdit) and misc.safe(self:get_completion_item().textEdit.replace) then - replace_range = self:get_completion_item().textEdit.replace + if misc.safe(self:get_completion_item().textEdit) then + if misc.safe(self:get_completion_item().textEdit.replace) then + replace_range = self:get_completion_item().textEdit.replace + else + replace_range = self:get_completion_item().textEdit.range + end else replace_range = { start = { line = self.source_replace_range.start.line, - character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), self.source_replace_range.start.character), + character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), + self.source_replace_range.start.character), }, ['end'] = self.source_replace_range['end'], } @@ -341,7 +356,7 @@ end ---Match line. ---@param input string ---@param matching_config cmp.MatchingConfig ----@return { score: number, matches: table[] } +---@return { score: integer, matches: table[] } entry.match = function(self, input, matching_config) return self.match_cache:ensure({ input, @@ -369,7 +384,7 @@ entry.match = function(self, input, matching_config) 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 + local accept = nil accept = accept or string.match(prefix, '^[^%a]+$') accept = accept or string.find(self:get_completion_item().textEdit.newText, prefix, 1, true) if accept then @@ -380,7 +395,7 @@ entry.match = function(self, input, matching_config) end 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, { synonyms = { self:get_word() } }) end return { score = score, matches = matches } @@ -422,13 +437,23 @@ entry.get_documentation = function(self) }) end - if type(item.documentation) == 'string' and item.documentation ~= '' then - table.insert(documents, { - kind = types.lsp.MarkupKind.PlainText, - value = str.trim(item.documentation), - }) - elseif type(item.documentation) == 'table' and item.documentation.value ~= '' then - table.insert(documents, item.documentation) + local documentation = item.documentation + if type(documentation) == 'string' and documentation ~= '' then + local value = str.trim(documentation) + if value ~= '' then + table.insert(documents, { + kind = types.lsp.MarkupKind.PlainText, + value = value, + }) + end + elseif type(documentation) == 'table' and not misc.empty(documentation.value) then + local value = str.trim(documentation.value) + if value ~= '' then + table.insert(documents, { + kind = documentation.kind, + value = value, + }) + end end return vim.lsp.util.convert_input_to_markdown_lines(documents) diff --git a/lua/cmp/init.lua b/lua/cmp/init.lua index ac8c05e13..b0b913021 100644 --- a/lua/cmp/init.lua +++ b/lua/cmp/init.lua @@ -50,7 +50,7 @@ end ---Register completion sources ---@param name string ---@param s cmp.Source ----@return number +---@return integer cmp.register_source = function(name, s) local src = source.new(name, s) cmp.core:register_source(src) @@ -58,7 +58,7 @@ cmp.register_source = function(name, s) end ---Unregister completion source ----@param id number +---@param id integer cmp.unregister_source = function(id) cmp.core:unregister_source(id) end @@ -108,7 +108,6 @@ cmp.close = cmp.sync(function() if cmp.core.view:visible() then local release = cmp.core:suspend() cmp.core.view:close() - cmp.core:reset() vim.schedule(release) return true else @@ -131,6 +130,7 @@ end) ---Select next item if possible cmp.select_next_item = cmp.sync(function(option) option = option or {} + option.behavior = option.behavior or cmp.SelectBehavior.Insert if cmp.core.view:visible() then local release = cmp.core:suspend() @@ -138,8 +138,7 @@ cmp.select_next_item = cmp.sync(function(option) vim.schedule(release) return true elseif vim.fn.pumvisible() == 1 then - -- Special handling for native pum. Required to facilitate key mapping processing. - if (option.behavior or cmp.SelectBehavior.Insert) == cmp.SelectBehavior.Insert then + if option.behavior == cmp.SelectBehavior.Insert then feedkeys.call(keymap.t(''), 'in') else feedkeys.call(keymap.t(''), 'in') @@ -152,6 +151,7 @@ end) ---Select prev item if possible cmp.select_prev_item = cmp.sync(function(option) option = option or {} + option.behavior = option.behavior or cmp.SelectBehavior.Insert if cmp.core.view:visible() then local release = cmp.core:suspend() @@ -159,8 +159,7 @@ cmp.select_prev_item = cmp.sync(function(option) vim.schedule(release) return true elseif vim.fn.pumvisible() == 1 then - -- Special handling for native pum. Required to facilitate key mapping processing. - if (option.behavior or cmp.SelectBehavior.Insert) == cmp.SelectBehavior.Insert then + if option.behavior == cmp.SelectBehavior.Insert then feedkeys.call(keymap.t(''), 'in') else feedkeys.call(keymap.t(''), 'in') @@ -182,7 +181,7 @@ end) ---Scrolling documentation window if possible cmp.scroll_docs = cmp.sync(function(delta) - if cmp.core.view:visible() then + if cmp.core.view.docs_view:visible() then cmp.core.view:scroll_docs(delta) return true else @@ -193,25 +192,35 @@ end) ---Confirm completion cmp.confirm = cmp.sync(function(option, callback) option = option or {} - callback = callback or function() end - - local e = cmp.core.view:get_selected_entry() or (option.select and cmp.core.view:get_first_entry() or nil) - if e then - cmp.core:confirm(e, { - behavior = option.behavior, - }, function() - callback() - cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.TriggerOnly })) - end) - return true - else - -- Special handling for native puma. Required to facilitate key mapping processing. - if vim.fn.complete_info({ 'selected' }).selected ~= -1 then - feedkeys.call(keymap.t(''), 'in') + option.select = option.select or false + option.behavior = option.behavior or cmp.get_config().confirmation.default_behavior or cmp.ConfirmBehavior.Insert + callback = callback or (function() end) + + if cmp.core.view:visible() then + local e = cmp.core.view:get_selected_entry() + if not e and option.select then + e = cmp.core.view:get_first_entry() + end + if e then + cmp.core:confirm(e, { + behavior = option.behavior, + }, function() + callback() + cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.TriggerOnly })) + end) + return true + end + elseif vim.fn.pumvisible() == 1 then + local index = vim.fn.complete_info({ 'selected' }).selected + if index == -1 and option.select then + index = 0 + end + if index ~= -1 then + vim.api.nvim_select_popupmenu_item(index, true, true, {}) return true end - return false end + return false end) ---Show status diff --git a/lua/cmp/matcher.lua b/lua/cmp/matcher.lua index 7a22d9eb0..8a3396e9e 100644 --- a/lua/cmp/matcher.lua +++ b/lua/cmp/matcher.lua @@ -79,7 +79,7 @@ end ---@param input string ---@param word string ---@param option { synonyms: string[], disallow_fuzzy_matching: boolean, disallow_partial_matching: boolean, disallow_prefix_unmatching: boolean } ----@return number +---@return integer matcher.match = function(input, word, option) option = option or {} diff --git a/lua/cmp/source.lua b/lua/cmp/source.lua index bee063446..9a364ec71 100644 --- a/lua/cmp/source.lua +++ b/lua/cmp/source.lua @@ -10,16 +10,16 @@ local pattern = require('cmp.utils.pattern') local char = require('cmp.utils.char') ---@class cmp.Source ----@field public id number +---@field public id integer ---@field public name string ---@field public source any ---@field public cache cmp.Cache ----@field public revision number +---@field public revision integer ---@field public incomplete boolean ---@field public is_triggered_by_symbol boolean ---@field public entries cmp.Entry[] ----@field public offset number ----@field public request_offset number +---@field public offset integer +---@field public request_offset integer ---@field public context cmp.Context ---@field public completion_context lsp.CompletionContext|nil ---@field public status cmp.SourceStatus @@ -101,6 +101,8 @@ source.get_entries = function(self, ctx) return self.entries end)() + local entry_filter = self:get_entry_filter() + local inputs = {} local entries = {} for _, e in ipairs(target_entries) do @@ -115,7 +117,10 @@ source.get_entries = function(self, ctx) if e.score >= 1 then e.matches = match.matches e.exact = e:get_filter_text() == inputs[o] or e:get_word() == inputs[o] - table.insert(entries, e) + + if entry_filter(e, ctx) then + table.insert(entries, e) + end end end self.cache:set({ 'get_entries', self.revision, ctx.cursor_before_line }, entries) @@ -132,10 +137,10 @@ source.get_entries = function(self, ctx) end ---Get default insert range ----@return lsp.Range|nil +---@return lsp.Range source.get_default_insert_range = function(self) if not self.context then - return nil + error('context is not initialized yet.') end return self.cache:ensure({ 'get_default_insert_range', self.revision }, function() @@ -153,10 +158,10 @@ source.get_default_insert_range = function(self) end ---Get default replace range ----@return lsp.Range|nil +---@return lsp.Range source.get_default_replace_range = function(self) if not self.context then - return nil + error('context is not initialized yet.') end return self.cache:ensure({ 'get_default_replace_range', self.revision }, function() @@ -223,7 +228,7 @@ source.get_keyword_pattern = function(self) end ---Get keyword_length ----@return number +---@return integer source.get_keyword_length = function(self) local c = self:get_source_config() if c.keyword_length then @@ -232,6 +237,16 @@ source.get_keyword_length = function(self) return config.get().completion.keyword_length or 1 end +---Get filter +--@return function +source.get_entry_filter = function(self) + local c = self:get_source_config() + if c.entry_filter then + return c.entry_filter + end + return function(_, _) return true end +end + ---Invoke completion ---@param ctx cmp.Context ---@param callback function diff --git a/lua/cmp/types/cmp.lua b/lua/cmp/types/cmp.lua index 8759aca5e..1e33f2238 100644 --- a/lua/cmp/types/cmp.lua +++ b/lua/cmp/types/cmp.lua @@ -12,7 +12,7 @@ cmp.SelectBehavior = { Select = 'select', } ----@alias cmp.ContextReason 'auto' | 'manual' 'triggerOnly' | 'none' +---@alias cmp.ContextReason 'auto' | 'manual' | 'triggerOnly' | 'none' cmp.ContextReason = { Auto = 'auto', Manual = 'manual', @@ -51,7 +51,7 @@ cmp.ItemField = { ---@class cmp.SnippetExpansionParams ---@field public body string ----@field public insert_text_mode number +---@field public insert_text_mode integer ---@class cmp.CompleteParams ---@field public reason? cmp.ContextReason @@ -67,7 +67,7 @@ cmp.ItemField = { ---@class cmp.SourceApiParams: cmp.SourceConfig ---@class cmp.SourceCompletionApiParams : cmp.SourceConfig ----@field public offset number +---@field public offset integer ---@field public context cmp.Context ---@field public completion_context lsp.CompletionContext @@ -78,8 +78,9 @@ cmp.ItemField = { ---@field public s nil|function(fallback: function): void ---@class cmp.ConfigSchema ----@field private revision number +---@field private revision integer ---@field public enabled fun():boolean|boolean +---@field public performance cmp.PerformanceConfig ---@field public preselect cmp.PreselectMode ---@field public completion cmp.CompletionConfig ---@field public window cmp.WindowConfig|nil @@ -93,6 +94,11 @@ cmp.ItemField = { ---@field public view cmp.ViewConfig ---@field public experimental cmp.ExperimentalConfig +---@class cmp.PerformanceConfig +---@field public debounce integer +---@field public throttle integer +---@field public fetching_timeout integer + ---@class cmp.WindowConfig ---@field completion cmp.WindowConfig ---@field documentation cmp.WindowConfig|nil @@ -101,15 +107,15 @@ cmp.ItemField = { ---@field public autocomplete cmp.TriggerEvent[] ---@field public completeopt string ---@field public get_trigger_characters fun(trigger_characters: string[]): string[] ----@field public keyword_length number +---@field public keyword_length integer ---@field public keyword_pattern string ---@class cmp.WindowConfig ---@field public border string|string[] ---@field public winhighlight string ----@field public zindex number|nil ----@field public max_width number|nil ----@field public max_height number|nil +---@field public zindex integer|nil +---@field public max_width integer|nil +---@field public max_height integer|nil ---@class cmp.ConfirmationConfig ---@field public default_behavior cmp.ConfirmBehavior @@ -121,7 +127,7 @@ cmp.ItemField = { ---@field public disallow_prefix_unmatching boolean ---@class cmp.SortingConfig ----@field public priority_weight number +---@field public priority_weight integer ---@field public comparators function[] ---@class cmp.FormattingConfig @@ -140,12 +146,13 @@ cmp.ItemField = { ---@class cmp.SourceConfig ---@field public name string ---@field public option table|nil ----@field public priority number|nil +---@field public priority integer|nil ---@field public trigger_characters string[]|nil ---@field public keyword_pattern string|nil ----@field public keyword_length number|nil ----@field public max_item_count number|nil ----@field public group_index number|nil +---@field public keyword_length integer|nil +---@field public max_item_count integer|nil +---@field public group_index integer|nil +---@field public entry_filter nil|function(entry: cmp.Entry, ctx: cmp.Context): boolean ---@class cmp.ViewConfig ---@field public entries cmp.EntriesConfig diff --git a/lua/cmp/types/lsp.lua b/lua/cmp/types/lsp.lua index 4af54a979..001207a71 100644 --- a/lua/cmp/types/lsp.lua +++ b/lua/cmp/types/lsp.lua @@ -6,7 +6,7 @@ local lsp = {} lsp.Position = { ---Convert lsp.Position to vim.Position - ---@param buf number|string + ---@param buf integer ---@param position lsp.Position ---@return vim.Position to_vim = function(buf, position) @@ -26,7 +26,7 @@ lsp.Position = { } end, ---Convert vim.Position to lsp.Position - ---@param buf number|string + ---@param buf integer ---@param position vim.Position ---@return lsp.Position to_lsp = function(buf, position) @@ -49,7 +49,7 @@ lsp.Position = { lsp.Range = { ---Convert lsp.Range to vim.Range - ---@param buf number|string + ---@param buf integer|string ---@param range lsp.Range ---@return vim.Range to_vim = function(buf, range) @@ -60,7 +60,7 @@ lsp.Range = { end, ---Convert vim.Range to lsp.Range - ---@param buf number|string + ---@param buf integer|string ---@param range vim.Range ---@return lsp.Range to_lsp = function(buf, range) @@ -145,8 +145,8 @@ lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind) ---@field public value string ---@class lsp.Position ----@field public line number ----@field public character number +---@field public line integer +---@field public character integer ---@class lsp.Range ---@field public start lsp.Position @@ -161,15 +161,24 @@ lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind) ---@field public range lsp.Range|nil ---@field public newText string ----@class lsp.InsertReplaceTextEdit ----@field public insert lsp.Range|nil ----@field public replace lsp.Range|nil +---@alias lsp.InsertReplaceTextEdit lsp.internal.InsertTextEdit|lsp.internal.ReplaceTextEdit + +---@class lsp.internal.InsertTextEdit +---@field public insert lsp.Range +---@field public newText string + +---@class lsp.internal.ReplaceTextEdit +---@field public insert lsp.Range ---@field public newText string ---@class lsp.CompletionItemLabelDetails ---@field public detail string|nil ---@field public description string|nil +---@class lsp.Cmp +---@field public kind_text string +---@field public kind_hl_group string + ---@class lsp.CompletionItem ---@field public label string ---@field public labelDetails lsp.CompletionItemLabelDetails|nil @@ -189,6 +198,7 @@ lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind) ---@field public commitCharacters string[]|nil ---@field public command lsp.Command|nil ---@field public data any|nil +---@field public cmp lsp.Cmp|nil --- ---TODO: Should send the issue for upstream? ---@field public word string|nil diff --git a/lua/cmp/types/vim.lua b/lua/cmp/types/vim.lua index 95e757dcd..e9d907572 100644 --- a/lua/cmp/types/vim.lua +++ b/lua/cmp/types/vim.lua @@ -12,8 +12,8 @@ ---@field public menu_hl_group string|nil ---@class vim.Position ----@field public row number ----@field public col number +---@field public row integer +---@field public col integer ---@class vim.Range ---@field public start vim.Position diff --git a/lua/cmp/utils/api.lua b/lua/cmp/utils/api.lua index d0534092b..381482eb4 100644 --- a/lua/cmp/utils/api.lua +++ b/lua/cmp/utils/api.lua @@ -44,6 +44,7 @@ api.get_current_line = function() return vim.api.nvim_get_current_line() end +---@return { [1]: integer, [2]: integer } api.get_cursor = function() if api.is_cmdline_mode() then return { vim.o.lines - (vim.api.nvim_get_option('cmdheight') or 1) + 1, vim.fn.getcmdpos() - 1 } diff --git a/lua/cmp/utils/async.lua b/lua/cmp/utils/async.lua index 13f126ba3..dd9ced569 100644 --- a/lua/cmp/utils/async.lua +++ b/lua/cmp/utils/async.lua @@ -2,17 +2,31 @@ local async = {} ---@class cmp.AsyncThrottle ---@field public running boolean ----@field public timeout number ----@field public sync function(self: cmp.AsyncThrottle, timeout: number|nil) +---@field public timeout integer +---@field public sync function(self: cmp.AsyncThrottle, timeout: integer|nil) ---@field public stop function ---@field public __call function +local timers = {} + +vim.api.nvim_create_autocmd('VimLeavePre', { + callback = function() + for _, timer in pairs(timers) do + if timer and not timer:is_closing() then + timer:stop() + timer:close() + end + end + end +}) + ---@param fn function ----@param timeout number +---@param timeout integer ---@return cmp.AsyncThrottle async.throttle = function(fn, timeout) local time = nil local timer = vim.loop.new_timer() + timers[#timers+1] = timer return setmetatable({ running = false, timeout = timeout, @@ -60,7 +74,7 @@ end ---Timeout callback function ---@param fn function ----@param timeout number +---@param timeout integer ---@return function async.timeout = function(fn, timeout) local timer diff --git a/lua/cmp/utils/binary.lua b/lua/cmp/utils/binary.lua index c6a708868..b799f969e 100644 --- a/lua/cmp/utils/binary.lua +++ b/lua/cmp/utils/binary.lua @@ -12,7 +12,7 @@ end ---@param list any[] ---@param item any ---@param func fun(a: any, b: any): 1|-1|0 ----@return number +---@return integer binary.search = function(list, item, func) local s = 1 local e = #list diff --git a/lua/cmp/utils/buffer.lua b/lua/cmp/utils/buffer.lua index 63171c9a7..0705cbc74 100644 --- a/lua/cmp/utils/buffer.lua +++ b/lua/cmp/utils/buffer.lua @@ -2,7 +2,7 @@ local buffer = {} buffer.cache = {} ----@return number buf +---@return integer buf buffer.get = function(name) local buf = buffer.cache[name] if buf and vim.api.nvim_buf_is_valid(buf) then @@ -12,7 +12,7 @@ buffer.get = function(name) end end ----@return number buf +---@return integer buf ---@return boolean created_new buffer.ensure = function(name) local created_new = false diff --git a/lua/cmp/utils/cache.lua b/lua/cmp/utils/cache.lua index 8607b2a3f..26456ad4b 100644 --- a/lua/cmp/utils/cache.lua +++ b/lua/cmp/utils/cache.lua @@ -9,7 +9,7 @@ cache.new = function() end ---Get cache value ----@param key string +---@param key string|string[] ---@return any|nil cache.get = function(self, key) key = self:key(key) @@ -20,7 +20,7 @@ cache.get = function(self, key) end ---Set cache value explicitly ----@param key string +---@param key string|string[] ---@vararg any cache.set = function(self, key, value) key = self:key(key) @@ -28,8 +28,10 @@ cache.set = function(self, key, value) end ---Ensure value by callback ----@param key string ----@param callback fun(): any +---@generic T +---@param key string|string[] +---@param callback fun(): T +---@return T cache.ensure = function(self, key, callback) local value = self:get(key) if value == nil then @@ -46,7 +48,7 @@ cache.clear = function(self) end ---Create key ----@param key string|table +---@param key string|string[] ---@return string cache.key = function(_, key) if type(key) == 'table' then diff --git a/lua/cmp/utils/char.lua b/lua/cmp/utils/char.lua index 6e18994a3..c733bef31 100644 --- a/lua/cmp/utils/char.lua +++ b/lua/cmp/utils/char.lua @@ -22,50 +22,50 @@ end) local char = {} ----@param byte number +---@param byte integer ---@return boolean char.is_upper = function(byte) return ALPHA[byte] end ----@param byte number +---@param byte integer ---@return boolean char.is_alpha = function(byte) return alpha[byte] or ALPHA[byte] end ----@param byte number +---@param byte integer ---@return boolean char.is_digit = function(byte) return digit[byte] end ----@param byte number +---@param byte integer ---@return boolean char.is_white = function(byte) return white[byte] end ----@param byte number +---@param byte integer ---@return boolean char.is_symbol = function(byte) return not (char.is_alnum(byte) or char.is_white(byte)) end ----@param byte number +---@param byte integer ---@return boolean char.is_printable = function(byte) return string.match(string.char(byte), '^%c$') == nil end ----@param byte number +---@param byte integer ---@return boolean char.is_alnum = function(byte) return char.is_alpha(byte) or char.is_digit(byte) end ---@param text string ----@param index number +---@param index integer ---@return boolean char.is_semantic_index = function(text, index) if index <= 1 then @@ -91,8 +91,8 @@ char.is_semantic_index = function(text, index) end ---@param text string ----@param current_index number ----@return boolean +---@param current_index integer +---@return integer char.get_next_semantic_index = function(text, current_index) for i = current_index + 1, #text do if char.is_semantic_index(text, i) then @@ -103,8 +103,8 @@ char.get_next_semantic_index = function(text, current_index) end ---Ignore case match ----@param byte1 number ----@param byte2 number +---@param byte1 integer +---@param byte2 integer ---@return boolean char.match = function(byte1, byte2) if not char.is_alpha(byte1) or not char.is_alpha(byte2) then diff --git a/lua/cmp/utils/keymap.lua b/lua/cmp/utils/keymap.lua index aea5c1db4..4ef23cbd6 100644 --- a/lua/cmp/utils/keymap.lua +++ b/lua/cmp/utils/keymap.lua @@ -68,7 +68,7 @@ keymap.undojoin = function() end ---Create backspace keys. ----@param count number +---@param count string|integer ---@return string keymap.backspace = function(count) if type(count) == 'string' then @@ -83,7 +83,7 @@ keymap.backspace = function(count) end ---Update indentkeys. ----@param expr string +---@param expr? string ---@return string keymap.indentkeys = function(expr) return string.format(keymap.t('set indentkeys=%s'), expr and vim.fn.escape(expr, '| \t\\') or '') @@ -102,8 +102,7 @@ keymap.listen = function(mode, lhs, callback) lhs = keymap.normalize(keymap.to_keymap(lhs)) local existing = keymap.get_map(mode, lhs) - local id = string.match(existing.rhs, 'v:lua%.cmp%.utils%.keymap%.set_map%((%d+)%)') - if id and keymap.set_map.callbacks[tonumber(id, 10)] then + if existing.desc == 'cmp.utils.keymap.set_map' then return end @@ -128,8 +127,8 @@ end keymap.fallback = function(bufnr, mode, map) return function() if map.expr then - local fallback_expr = string.format('(cmp.u.k.fallback_expr:%s)', map.lhs) - keymap.set_map(bufnr, mode, fallback_expr, function() + local fallback_lhs = string.format('(cmp.u.k.fallback_expr:%s)', map.lhs) + keymap.set_map(bufnr, mode, fallback_lhs, function() return keymap.solve(bufnr, mode, map).keys end, { expr = true, @@ -138,12 +137,12 @@ keymap.fallback = function(bufnr, mode, map) nowait = map.nowait, silent = map.silent and mode ~= 'c', }) - vim.api.nvim_feedkeys(keymap.t(fallback_expr), 'im', true) - elseif not map.callback then + vim.api.nvim_feedkeys(keymap.t(fallback_lhs), 'im', true) + elseif map.callback then + map.callback() + else local solved = keymap.solve(bufnr, mode, map) vim.api.nvim_feedkeys(solved.keys, solved.mode, true) - else - map.callback() end end end @@ -151,7 +150,14 @@ end ---Solve keymap.solve = function(bufnr, mode, map) local lhs = keymap.t(map.lhs) - local rhs = map.expr and (map.callback and map.callback() or vim.api.nvim_eval(keymap.t(map.rhs))) or keymap.t(map.rhs) + local rhs = keymap.t(map.rhs) + if map.expr then + if map.callback then + rhs = map.callback() + else + rhs = vim.api.nvim_eval(keymap.t(map.rhs)) + end + end if map.noremap then return { keys = rhs, mode = 'in' } @@ -161,7 +167,7 @@ keymap.solve = function(bufnr, mode, map) local recursive = string.format('0_(cmp.u.k.recursive:%s)', lhs) keymap.set_map(bufnr, mode, recursive, lhs, { noremap = true, - script = map.script, + script = true, nowait = map.nowait, silent = map.silent and mode ~= 'c', }) @@ -223,29 +229,18 @@ keymap.get_map = function(mode, lhs) end ---Set keymapping -keymap.set_map = setmetatable({ - callbacks = {}, -}, { - __call = function(self, bufnr, mode, lhs, rhs, opts) - if type(rhs) == 'function' then - local id = misc.id('cmp.utils.keymap.set_map') - self.callbacks[id] = rhs - if opts.expr then - rhs = ('v:lua.cmp.utils.keymap.set_map(%s)'):format(id) - else - rhs = ('call v:lua.cmp.utils.keymap.set_map(%s)'):format(id) - end - end +keymap.set_map = function(bufnr, mode, lhs, rhs, opts) + if type(rhs) == 'function' then + opts.callback = rhs + rhs = '' + end + opts.desc = 'cmp.utils.keymap.set_map' - if bufnr == -1 then - vim.api.nvim_set_keymap(mode, lhs, rhs, opts) - else - vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts) - end - end, -}) -misc.set(_G, { 'cmp', 'utils', 'keymap', 'set_map' }, function(id) - return keymap.set_map.callbacks[id]() or '' -end) + if bufnr == -1 then + vim.api.nvim_set_keymap(mode, lhs, rhs, opts) + else + vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts) + end +end return keymap diff --git a/lua/cmp/utils/misc.lua b/lua/cmp/utils/misc.lua index 7c6d0e7a8..1b6f393a3 100644 --- a/lua/cmp/utils/misc.lua +++ b/lua/cmp/utils/misc.lua @@ -32,7 +32,7 @@ end ---Repeat values ---@generic T ---@param str_or_tbl T ----@param count number +---@param count integer ---@return T misc.rep = function(str_or_tbl, count) if type(str_or_tbl) == 'string' then @@ -124,8 +124,9 @@ misc.id = setmetatable({ }) ---Check the value is nil or not. ----@param v boolean ----@return boolean +---@generic T|nil|vim.NIL +---@param v T +---@return T|nil misc.safe = function(v) if v == nil or v == vim.NIL then return nil @@ -184,8 +185,8 @@ end ---Safe version of vim.str_utfindex ---@param text string ----@param vimindex number|nil ----@return number +---@param vimindex integer|nil +---@return integer misc.to_utfindex = function(text, vimindex) vimindex = vimindex or #text + 1 return vim.str_utfindex(text, math.max(0, math.min(vimindex - 1, #text))) @@ -193,8 +194,8 @@ end ---Safe version of vim.str_byteindex ---@param text string ----@param utfindex number ----@return number +---@param utfindex integer +---@return integer misc.to_vimindex = function(text, utfindex) utfindex = utfindex or #text for i = utfindex, 1, -1 do @@ -224,12 +225,14 @@ end misc.redraw = setmetatable({ doing = false, force = false, - termcode = vim.api.nvim_replace_termcodes('', true, true, true), + -- We use `` to redraw the screen. (Previously, We use . it will remove the unmatches search history.) + incsearch_redraw_keys = '', }, { __call = function(self, force) + local termcode = vim.api.nvim_replace_termcodes(self.incsearch_redraw_keys, true, true, true) if vim.tbl_contains({ '/', '?' }, vim.fn.getcmdtype()) then if vim.o.incsearch then - return vim.api.nvim_feedkeys(self.termcode, 'in', true) + return vim.api.nvim_feedkeys(termcode, 'in', true) end end diff --git a/lua/cmp/utils/str.lua b/lua/cmp/utils/str.lua index bca210c07..df5e643bd 100644 --- a/lua/cmp/utils/str.lua +++ b/lua/cmp/utils/str.lua @@ -99,8 +99,8 @@ end ---get_word ---@param text string ----@param stop_char number ----@param min_length number +---@param stop_char integer +---@param min_length integer ---@return string str.get_word = function(text, stop_char, min_length) min_length = min_length or 0 diff --git a/lua/cmp/utils/window.lua b/lua/cmp/utils/window.lua index a8a271e4e..b150c03ca 100644 --- a/lua/cmp/utils/window.lua +++ b/lua/cmp/utils/window.lua @@ -5,18 +5,18 @@ local api = require('cmp.utils.api') ---@class cmp.WindowStyle ---@field public relative string ----@field public row number ----@field public col number ----@field public width number ----@field public height number +---@field public row integer +---@field public col integer +---@field public width integer +---@field public height integer ---@field public border string|string[]|nil ----@field public zindex number|nil +---@field public zindex integer|nil ---@class cmp.Window ---@field public name string ----@field public win number|nil ----@field public thumb_win number|nil ----@field public sbar_win number|nil +---@field public win integer|nil +---@field public thumb_win integer|nil +---@field public sbar_win integer|nil ---@field public style cmp.WindowStyle ---@field public opt table ---@field public buffer_opt table @@ -91,7 +91,7 @@ window.set_style = function(self, style) end ---Return buffer id. ----@return number +---@return integer window.get_buffer = function(self) local buf, created_new = buffer.ensure(self.name) if created_new then @@ -240,7 +240,7 @@ window.info = function(self) end ---Return border information. ----@return { top: number, left: number, right: number, bottom: number, vert: number, horiz: number, visible: boolean } +---@return { top: integer, left: integer, right: integer, bottom: integer, vert: integer, horiz: integer, visible: boolean } window.get_border_info = function(self) local border = self.style.border if not border or border == 'none' then @@ -296,7 +296,7 @@ end ---Get scroll height. ---NOTE: The result of vim.fn.strdisplaywidth depends on the buffer it was called in (see comment in cmp.Entry.get_view). ----@return number +---@return integer window.get_content_height = function(self) if not self:option('wrap') then return vim.api.nvim_buf_line_count(self:get_buffer()) diff --git a/lua/cmp/view.lua b/lua/cmp/view.lua index 981378b9d..42f5664eb 100644 --- a/lua/cmp/view.lua +++ b/lua/cmp/view.lua @@ -108,6 +108,9 @@ view.open = function(self, ctx, sources) -- open if #entries > 0 then self:_get_entries_view():open(offset, entries) + self.event:emit('menu_opened', { + window = self:_get_entries_view(), + }) break end end @@ -128,6 +131,9 @@ view.close = function(self) self:_get_entries_view():close() self.docs_view:close() self.ghost_text_view:hide() + self.event:emit('menu_closed', { + window = self:_get_entries_view(), + }) end ---Abort menu @@ -135,6 +141,9 @@ view.abort = function(self) self:_get_entries_view():abort() self.docs_view:close() self.ghost_text_view:hide() + self.event:emit('menu_closed', { + window = self:_get_entries_view(), + }) end ---Return the view is visible or not. @@ -144,7 +153,7 @@ view.visible = function(self) end ---Scroll documentation window if possible. ----@param delta number +---@param delta integer view.scroll_docs = function(self, delta) self.docs_view:scroll(delta) end diff --git a/lua/cmp/view/custom_entries_view.lua b/lua/cmp/view/custom_entries_view.lua index 8020a9b0a..2c72fe6f0 100644 --- a/lua/cmp/view/custom_entries_view.lua +++ b/lua/cmp/view/custom_entries_view.lua @@ -8,13 +8,11 @@ local keymap = require('cmp.utils.keymap') local misc = require('cmp.utils.misc') local api = require('cmp.utils.api') -local SIDE_PADDING = 1 - local DEFAULT_HEIGHT = 10 -- @see https://github.com/vim/vim/blob/master/src/popupmenu.c#L45 ---@class cmp.CustomEntriesView ---@field private entries_win cmp.Window ----@field private offset number +---@field private offset integer ---@field private active boolean ---@field private entries cmp.Entry[] ---@field private column_width any @@ -39,6 +37,7 @@ custom_entries_view.new = function() -- always rendered one column wide, which removes the unpredictability coming -- from variable width of the tab character. self.entries_win:buffer_option('tabstop', 1) + self.entries_win:buffer_option('filetype', 'cmp_menu') self.event = event.new() self.offset = -1 self.active = false @@ -65,7 +64,7 @@ custom_entries_view.new = function() local e = self.entries[i + 1] if e then local v = e:get_view(self.offset, buf) - local o = SIDE_PADDING + local o = config.get().window.completion.side_padding local a = 0 for _, field in ipairs(fields) do if field == types.cmp.ItemField.Abbr then @@ -126,7 +125,7 @@ custom_entries_view.open = function(self, offset, entries) local entries_buf = self.entries_win:get_buffer() local lines = {} local dedup = {} - local preselect = 0 + local preselect_index = 0 for _, e in ipairs(entries) do local view = e:get_view(offset, entries_buf) if view.dup == 1 or not dedup[e.completion_item.label] then @@ -136,8 +135,8 @@ custom_entries_view.open = function(self, offset, entries) self.column_width.menu = math.max(self.column_width.menu, view.menu.width) table.insert(self.entries, e) table.insert(lines, ' ') - if preselect == 0 and e.completion_item.preselect then - preselect = #self.entries + if preselect_index == 0 and e.completion_item.preselect then + preselect_index = #self.entries end end end @@ -188,8 +187,8 @@ custom_entries_view.open = function(self, offset, entries) for i = 1, math.floor(n / 2) do self.entries[i], self.entries[n - i + 1] = self.entries[n - i + 1], self.entries[i] end - if preselect ~= 0 then - preselect = #self.entries - preselect + 1 + if preselect_index ~= 0 then + preselect_index = #self.entries - preselect_index + 1 end end @@ -200,7 +199,7 @@ custom_entries_view.open = function(self, offset, entries) relative = 'editor', style = 'minimal', row = math.max(0, row), - col = math.max(0, col), + col = math.max(0, col + completion.col_offset), width = width, height = height, border = completion.border, @@ -208,8 +207,8 @@ custom_entries_view.open = function(self, offset, entries) }) -- always set cursor when starting. It will be adjusted on the call to _select vim.api.nvim_win_set_cursor(self.entries_win.win, { 1, 0 }) - if preselect > 0 and config.get().preselect == types.cmp.PreselectMode.Item then - self:_select(preselect, { behavior = types.cmp.SelectBehavior.Select }) + if preselect_index > 0 and config.get().preselect == types.cmp.PreselectMode.Item then + self:_select(preselect_index, { behavior = types.cmp.SelectBehavior.Select }) elseif not string.match(config.get().completion.completeopt, 'noselect') then if self:is_direction_top_down() then self:_select(1, { behavior = types.cmp.SelectBehavior.Select }) @@ -255,12 +254,12 @@ custom_entries_view.draw = function(self) if e then local view = e:get_view(self.offset, entries_buf) local text = {} - table.insert(text, string.rep(' ', SIDE_PADDING)) + table.insert(text, string.rep(' ', config.get().window.completion.side_padding)) for _, field in ipairs(fields) do table.insert(text, view[field].text) table.insert(text, string.rep(' ', 1 + self.column_width[field] - view[field].width)) end - table.insert(text, string.rep(' ', SIDE_PADDING)) + table.insert(text, string.rep(' ', config.get().window.completion.side_padding)) table.insert(texts, table.concat(text, '')) end end diff --git a/lua/cmp/view/docs_view.lua b/lua/cmp/view/docs_view.lua index 17af6d044..43ec304d1 100644 --- a/lua/cmp/view/docs_view.lua +++ b/lua/cmp/view/docs_view.lua @@ -23,6 +23,7 @@ docs_view.new = function() self.window:buffer_option('buftype', 'nofile') self.window:buffer_option('bufhidden', 'wipe') self.window:buffer_option('swapfile', false) + self.window:buffer_option('filetype', 'cmp_docs') return self end @@ -60,6 +61,10 @@ docs_view.open = function(self, e, view) max_width = max_width, max_height = documentation.max_height, }) + vim.lsp.util.stylize_markdown(0, documents, { + max_width = max_width - border_info.horiz, + max_height = documentation.max_height, + }) vim.api.nvim_buf_set_option(0, 'modifiable', false) end) end diff --git a/lua/cmp/view/native_entries_view.lua b/lua/cmp/view/native_entries_view.lua index d8b0b1bad..643827c8b 100644 --- a/lua/cmp/view/native_entries_view.lua +++ b/lua/cmp/view/native_entries_view.lua @@ -7,10 +7,10 @@ local config = require('cmp.config') local api = require('cmp.utils.api') ---@class cmp.NativeEntriesView ----@field private offset number +---@field private offset integer ---@field private items vim.CompletedItem ---@field private entries cmp.Entry[] ----@field private preselect_index number +---@field private preselect_index integer ---@field public event cmp.Event local native_entries_view = {} @@ -77,8 +77,7 @@ native_entries_view.open = function(self, offset, entries) end native_entries_view.close = function(self) - if api.is_suitable_mode() and self:visible() then - vim.fn.complete(1, {}) + if api.is_insert_mode() and self:visible() then vim.api.nvim_select_popupmenu_item(-1, false, true, {}) end self.offset = -1 @@ -101,10 +100,10 @@ native_entries_view.info = function(self) if self:visible() then local info = vim.fn.pum_getpos() return { - width = info.width + (info.scrollable and 1 or 0), + width = info.width + (info.scrollbar and 1 or 0) + (info.col == 0 and 0 or 1), height = info.height, row = info.row, - col = info.col, + col = info.col == 0 and 0 or info.col - 1, } end end diff --git a/lua/cmp/view/wildmenu_entries_view.lua b/lua/cmp/view/wildmenu_entries_view.lua index 3419164f4..62ee33083 100644 --- a/lua/cmp/view/wildmenu_entries_view.lua +++ b/lua/cmp/view/wildmenu_entries_view.lua @@ -9,7 +9,7 @@ local misc = require('cmp.utils.misc') local api = require('cmp.utils.api') ---@class cmp.CustomEntriesView ----@field private offset number +---@field private offset integer ---@field private entries_win cmp.Window ---@field private active boolean ---@field private entries cmp.Entry[] diff --git a/lua/cmp/vim_source.lua b/lua/cmp/vim_source.lua index 2ee8fbf37..5af9a72a3 100644 --- a/lua/cmp/vim_source.lua +++ b/lua/cmp/vim_source.lua @@ -2,7 +2,7 @@ local misc = require('cmp.utils.misc') local vim_source = {} ----@param id number +---@param id integer ---@param args any[] vim_source.on_callback = function(id, args) if vim_source.to_callback.callbacks[id] then @@ -11,7 +11,7 @@ vim_source.on_callback = function(id, args) end ---@param callback function ----@return number +---@return integer vim_source.to_callback = setmetatable({ callbacks = {}, }, { @@ -36,7 +36,7 @@ vim_source.to_args = function(args) return args end ----@param bridge_id number +---@param bridge_id integer ---@param methods string[] vim_source.new = function(bridge_id, methods) local self = {} diff --git a/nvim-cmp-scm-1.rockspec b/nvim-cmp-scm-1.rockspec new file mode 100644 index 000000000..2c72b0e49 --- /dev/null +++ b/nvim-cmp-scm-1.rockspec @@ -0,0 +1,38 @@ +local MODREV, SPECREV = "scm", "-1" +rockspec_format = "3.0" +package = "nvim-cmp" +version = MODREV .. SPECREV + +description = { + summary = "A completion plugin for neovim", + labels = { "neovim" }, + detailed = [[ + A completion engine plugin for neovim written in Lua. Completion sources are installed from external repositories and "sourced". + ]], + homepage = "https://github.com/hrsh7th/nvim-cmp", + license = "MIT", +} + +dependencies = { + "lua >= 5.1, < 5.4", +} + +source = { + url = "http://github.com/hrsh7th/nvim-cmp/archive/v" .. MODREV .. ".zip", + dir = "nvim-cmp-" .. MODREV, +} + +if MODREV == "scm" then + source = { + url = "git://github.com/hrsh7th/nvim-cmp", + } +end + +build = { + type = "builtin", +} +copy_directories = { + 'lua', + 'autoload', + 'plugin', +} diff --git a/plugin/cmp.lua b/plugin/cmp.lua index caf8c2758..acffc4349 100644 --- a/plugin/cmp.lua +++ b/plugin/cmp.lua @@ -26,15 +26,15 @@ for kind in pairs(types.lsp.CompletionItemKind) do end autocmd.subscribe('ColorScheme', function() - highlight.inherit('CmpItemAbbrDefault', 'Pmenu', { bg = 'NONE', default = true }) - highlight.inherit('CmpItemAbbrDeprecatedDefault', 'Comment', { bg = 'NONE', default = true }) - highlight.inherit('CmpItemAbbrMatchDefault', 'Pmenu', { bg = 'NONE', default = true }) - highlight.inherit('CmpItemAbbrMatchFuzzyDefault', 'Pmenu', { bg = 'NONE', default = true }) - highlight.inherit('CmpItemKindDefault', 'Special', { bg = 'NONE', default = true }) - highlight.inherit('CmpItemMenuDefault', 'Pmenu', { bg = 'NONE', default = true }) + highlight.inherit('CmpItemAbbrDefault', 'Pmenu', { bg = 'NONE', default = false }) + highlight.inherit('CmpItemAbbrDeprecatedDefault', 'Comment', { bg = 'NONE', default = false }) + highlight.inherit('CmpItemAbbrMatchDefault', 'Pmenu', { bg = 'NONE', default = false }) + highlight.inherit('CmpItemAbbrMatchFuzzyDefault', 'Pmenu', { bg = 'NONE', default = false }) + highlight.inherit('CmpItemKindDefault', 'Special', { bg = 'NONE', default = false }) + highlight.inherit('CmpItemMenuDefault', 'Pmenu', { bg = 'NONE', default = false }) for name in pairs(types.lsp.CompletionItemKind) do if type(name) == 'string' then - vim.api.nvim_set_hl(0, ('CmpItemKind%sDefault'):format(name), { link = 'CmpItemKind', default = true }) + vim.api.nvim_set_hl(0, ('CmpItemKind%sDefault'):format(name), { link = 'CmpItemKind', default = false }) end end end) diff --git a/utils/vimrc.vim b/utils/vimrc.vim index a83e71d31..cc940f3d7 100644 --- a/utils/vimrc.vim +++ b/utils/vimrc.vim @@ -36,10 +36,10 @@ cmp.setup { [''] = cmp.mapping.confirm({ select = true }) }, - sources = { + sources = cmp.config.sources({ { name = "nvim_lsp" }, { name = "buffer" }, - }, + }), } EOF