mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-23 07:00:04 +08:00
feat(autocomplete): add nvim-cmp support
This commit is contained in:
parent
8e4183185c
commit
6dbd97087f
@ -315,7 +315,9 @@ let g:spacevim_realtime_leader_guide = 1
|
||||
" let g:spacevim_enable_key_frequency = 1
|
||||
" <
|
||||
let g:spacevim_enable_key_frequency = 0
|
||||
if (has('python3')
|
||||
if has('nvim-0.5.0')
|
||||
let g:spacevim_autocomplete_method = 'nvim-cmp'
|
||||
elseif (has('python3')
|
||||
\ && (SpaceVim#util#haspy3lib('neovim')
|
||||
\ || SpaceVim#util#haspy3lib('pynvim'))) &&
|
||||
\ (has('nvim') || (has('patch-8.0.0027')))
|
||||
@ -1384,7 +1386,6 @@ function! SpaceVim#end() abort
|
||||
elseif g:spacevim_vim_help_language ==# 'ja'
|
||||
let &helplang = 'jp'
|
||||
endif
|
||||
""
|
||||
" generate tags for SpaceVim
|
||||
let help = fnamemodify(g:_spacevim_root_dir, ':p:h') . '/doc'
|
||||
try
|
||||
@ -1392,8 +1393,6 @@ function! SpaceVim#end() abort
|
||||
catch
|
||||
call SpaceVim#logger#warn('Failed to generate helptags for SpaceVim')
|
||||
endtry
|
||||
|
||||
""
|
||||
" set language
|
||||
if !empty(g:spacevim_language)
|
||||
silent exec 'lan ' . g:spacevim_language
|
||||
|
@ -52,7 +52,7 @@ endfunction
|
||||
function! s:self._build_msg(msg, l) abort
|
||||
let msg = a:msg
|
||||
let time = strftime('%H:%M:%S')
|
||||
let log = printf('[ %s ] [%s] [%00.3f] [ %s ] %s',
|
||||
let log = printf('[ %s ] [%s] [%00.3f] [ %5s ] %s',
|
||||
\ self.name,
|
||||
\ time,
|
||||
\ reltimefloat(reltime(self.clock)),
|
||||
|
@ -12,6 +12,7 @@ let s:VIM = SpaceVim#api#import('vim')
|
||||
|
||||
"autocmds
|
||||
function! SpaceVim#autocmds#init() abort
|
||||
call SpaceVim#logger#debug('init SpaceVim_core autocmd group')
|
||||
augroup SpaceVim_core
|
||||
au!
|
||||
autocmd BufWinEnter quickfix nnoremap <silent> <buffer>
|
||||
|
@ -147,6 +147,7 @@ endfunction
|
||||
"}}}
|
||||
|
||||
function! SpaceVim#default#layers() abort
|
||||
call SpaceVim#logger#debug('init default layer list.')
|
||||
call SpaceVim#layers#load('autocomplete')
|
||||
call SpaceVim#layers#load('checkers')
|
||||
call SpaceVim#layers#load('format')
|
||||
@ -159,6 +160,7 @@ function! SpaceVim#default#layers() abort
|
||||
endfunction
|
||||
|
||||
function! SpaceVim#default#keyBindings() abort
|
||||
call SpaceVim#logger#debug('init default key bindings.')
|
||||
" yank and paste
|
||||
if has('unnamedplus')
|
||||
xnoremap <Leader>y "+y
|
||||
|
@ -86,6 +86,22 @@ function! SpaceVim#layers#autocomplete#plugins() abort
|
||||
\ 'on_event' : 'InsertEnter',
|
||||
\ 'loadconf' : 1,
|
||||
\ }])
|
||||
elseif g:spacevim_autocomplete_method ==# 'nvim-cmp'
|
||||
" use bundle nvim-cmp
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/nvim-cmp', {
|
||||
\ 'merged' : 0,
|
||||
\ 'loadconf' : 1,
|
||||
\ }])
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/cmp-buffer', {
|
||||
\ 'merged' : 0,
|
||||
\ }])
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/cmp-path', {
|
||||
\ 'merged' : 0,
|
||||
\ }])
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/lspkind-nvim', {
|
||||
\ 'merged' : 0,
|
||||
\ 'loadconf' : 1,
|
||||
\ }])
|
||||
elseif g:spacevim_autocomplete_method ==# 'asyncomplete'
|
||||
call add(plugins, ['prabirshrestha/asyncomplete.vim', {
|
||||
\ 'loadconf' : 1,
|
||||
@ -120,9 +136,12 @@ function! SpaceVim#layers#autocomplete#plugins() abort
|
||||
\ 'on_event' : 'CompleteDone',
|
||||
\ 'loadconf_before' : 1,
|
||||
\ }])
|
||||
if g:spacevim_autocomplete_method !=# 'nvim-cmp'
|
||||
" this plugin use same namespace as nvim-cmp
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/CompleteParameter.vim',
|
||||
\ { 'merged' : 0}])
|
||||
endif
|
||||
endif
|
||||
return plugins
|
||||
endfunction
|
||||
|
||||
|
@ -108,6 +108,10 @@ function! SpaceVim#layers#lsp#plugins() abort
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/nvim-lspconfig', {'merged' : 0, 'loadconf' : 1}])
|
||||
if g:spacevim_autocomplete_method ==# 'deoplete'
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/deoplete-lsp', {'merged' : 0}])
|
||||
elseif g:spacevim_autocomplete_method ==# 'nvim-cmp'
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/cmp-nvim-lsp', {
|
||||
\ 'merged' : 0,
|
||||
\ }])
|
||||
endif
|
||||
elseif SpaceVim#layers#isLoaded('autocomplete') && get(g:, 'spacevim_autocomplete_method') ==# 'coc'
|
||||
" nop
|
||||
|
@ -7,6 +7,7 @@
|
||||
"=============================================================================
|
||||
|
||||
function! SpaceVim#mapping#g#init() abort
|
||||
call SpaceVim#logger#debug('init g key bindings')
|
||||
nnoremap <silent><nowait> [G] :<c-u>LeaderGuide "g"<CR>
|
||||
nmap g [G]
|
||||
let g:_spacevim_mappings_g = {}
|
||||
|
@ -199,6 +199,7 @@ function! SpaceVim#mapping#leader#getName(key) abort
|
||||
endfunction
|
||||
|
||||
function! SpaceVim#mapping#leader#defindKEYs() abort
|
||||
call SpaceVim#logger#debug('defind SPC h k prefixs')
|
||||
let g:_spacevim_mappings_prefixs = {}
|
||||
if !g:spacevim_vimcompatible && !empty(g:spacevim_windows_leader)
|
||||
let g:_spacevim_mappings_prefixs[g:spacevim_windows_leader] = {'name' : '+Window prefix'}
|
||||
|
@ -11,6 +11,7 @@ let s:BUF = SpaceVim#api#import('vim#buffer')
|
||||
let s:file = expand('<sfile>:~')
|
||||
let s:funcbeginline = expand('<slnum>') + 1
|
||||
function! SpaceVim#mapping#space#init() abort
|
||||
call SpaceVim#logger#debug('init SPC key bindings')
|
||||
let g:_spacevim_mappings_space = {}
|
||||
let g:_spacevim_mappings_prefixs['[SPC]'] = {'name' : '+SPC prefix'}
|
||||
let g:_spacevim_mappings_space.t = {'name' : '+Toggles'}
|
||||
|
@ -20,8 +20,16 @@ if g:spacevim_snippet_engine ==# 'neosnippet'
|
||||
elseif neosnippet#expandable_or_jumpable() && getline('.')[col('.')-2] !=#'('
|
||||
return "\<plug>(neosnippet_expand_or_jump)"
|
||||
elseif pumvisible()
|
||||
\ ||
|
||||
\ (
|
||||
\ g:spacevim_autocomplete_method ==# 'nvim-cmp'
|
||||
\ && luaeval("require('cmp').visible()")
|
||||
\ )
|
||||
return "\<C-n>"
|
||||
elseif has('patch-7.4.774') && complete_parameter#jumpable(1) && getline('.')[col('.')-2] !=# ')'
|
||||
elseif has('patch-7.4.774')
|
||||
\ && g:spacevim_autocomplete_method !=# 'nvim-cmp'
|
||||
\ && complete_parameter#jumpable(1)
|
||||
\ && getline('.')[col('.')-2] !=# ')'
|
||||
return "\<plug>(complete_parameter#goto_next_parameter)"
|
||||
else
|
||||
return "\<tab>"
|
||||
|
@ -7,6 +7,7 @@
|
||||
"=============================================================================
|
||||
|
||||
function! SpaceVim#mapping#z#init() abort "{{{
|
||||
call SpaceVim#logger#debug('init z key bindings')
|
||||
nnoremap <silent><nowait> [Z] :<c-u>LeaderGuide "z"<CR>
|
||||
nmap z [Z]
|
||||
let g:_spacevim_mappings_z = {}
|
||||
|
@ -18,9 +18,10 @@ function! SpaceVim#plugins#load() abort
|
||||
|
||||
endfunction
|
||||
function! s:load_plugins() abort
|
||||
for group in SpaceVim#layers#get()
|
||||
let g:_spacevim_plugin_layer = group
|
||||
for plugin in s:getLayerPlugins(group)
|
||||
for layer in SpaceVim#layers#get()
|
||||
call SpaceVim#logger#debug('init ' . layer . ' layer plugins list.')
|
||||
let g:_spacevim_plugin_layer = layer
|
||||
for plugin in s:getLayerPlugins(layer)
|
||||
if len(plugin) == 2
|
||||
call SpaceVim#plugins#add(plugin[0], extend(plugin[1], {'overwrite' : 1}))
|
||||
if SpaceVim#plugins#tap(split(plugin[0], '/')[-1]) && get(plugin[1], 'loadconf', 0 )
|
||||
@ -33,7 +34,7 @@ function! s:load_plugins() abort
|
||||
call SpaceVim#plugins#add(plugin[0], {'overwrite' : 1})
|
||||
endif
|
||||
endfor
|
||||
call s:loadLayerConfig(group)
|
||||
call s:loadLayerConfig(layer)
|
||||
endfor
|
||||
unlet g:_spacevim_plugin_layer
|
||||
for plugin in g:spacevim_custom_plugins
|
||||
@ -55,6 +56,7 @@ function! s:getLayerPlugins(layer) abort
|
||||
endfunction
|
||||
|
||||
function! s:loadLayerConfig(layer) abort
|
||||
call SpaceVim#logger#debug('load ' . a:layer . ' layer config.')
|
||||
try
|
||||
call SpaceVim#layers#{a:layer}#config()
|
||||
catch /^Vim\%((\a\+)\)\=:E117/
|
||||
|
@ -16,3 +16,4 @@ In `bundle/` directory, there are two kinds of plugins: forked plugins without c
|
||||
- [indent-blankline.nvim](https://github.com/lukas-reineke/indent-blankline.nvim/tree/17a83ea765831cb0cc64f768b8c3f43479b90bbe)
|
||||
- [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/tree/507f8a570ac2b8b8dabdd0f62da3b3194bf822f8)
|
||||
- [deoplete-lsp](https://github.com/deoplete-plugins/deoplete-lsp/tree/6299a22bedfb4f814d95cb0010291501472f8fd0)
|
||||
- [nvim-cmp](https://github.com/hrsh7th/nvim-cmp/tree/1cfe2f7dfdd877b54c0f4b0f9a15f525e7a3ea01)
|
||||
|
21
bundle/cmp-buffer/LICENSE
Normal file
21
bundle/cmp-buffer/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 hrsh7th
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
62
bundle/cmp-buffer/README.md
Normal file
62
bundle/cmp-buffer/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# cmp-buffer
|
||||
|
||||
nvim-cmp source for buffer words.
|
||||
|
||||
# Setup
|
||||
|
||||
```lua
|
||||
require'cmp'.setup {
|
||||
sources = {
|
||||
{ name = 'buffer' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
The below source configuration are available.
|
||||
|
||||
|
||||
### keyword_length (type: number)
|
||||
|
||||
_Default:_ `3`
|
||||
|
||||
Specify word length to gather.
|
||||
|
||||
|
||||
### keyword_pattern (type: string)
|
||||
|
||||
_Default:_ `[[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%([\-.]\w*\)*\)]]`
|
||||
|
||||
A vim's regular expression for creating a word list from buffer content.
|
||||
|
||||
You can set this to `\k\+` if you want to use the `iskeyword` option for recognizing words.
|
||||
|
||||
|
||||
### get_bufnrs (type: fun(): number[])
|
||||
|
||||
_Default:_ `function() return { vim.api.nvim_get_current_buf() } end`
|
||||
|
||||
A function that specifies the buffer numbers to complete.
|
||||
|
||||
You can use the following pre-defined recipes.
|
||||
|
||||
##### All buffers
|
||||
|
||||
```lua
|
||||
get_bufnrs = function()
|
||||
return vim.api.nvim_list_bufs()
|
||||
end
|
||||
```
|
||||
|
||||
##### Visible buffers
|
||||
|
||||
```lua
|
||||
get_bufnrs = function()
|
||||
local bufs = {}
|
||||
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
||||
bufs[vim.api.nvim_win_get_buf(win)] = true
|
||||
end
|
||||
return vim.tbl_keys(bufs)
|
||||
end
|
||||
```
|
2
bundle/cmp-buffer/after/plugin/cmp_buffer.lua
Normal file
2
bundle/cmp-buffer/after/plugin/cmp_buffer.lua
Normal file
@ -0,0 +1,2 @@
|
||||
require'cmp'.register_source('buffer', require'cmp_buffer'.new())
|
||||
|
235
bundle/cmp-buffer/lua/cmp_buffer/buffer.lua
Normal file
235
bundle/cmp-buffer/lua/cmp_buffer/buffer.lua
Normal file
@ -0,0 +1,235 @@
|
||||
---@class cmp_buffer.Buffer
|
||||
---@field public bufnr number
|
||||
---@field public regex any
|
||||
---@field public length number
|
||||
---@field public pattern string
|
||||
---@field public indexing_chunk_size number
|
||||
---@field public indexing_interval number
|
||||
---@field public timer any|nil
|
||||
---@field public lines_count number
|
||||
---@field public lines_words table<number, string[]>
|
||||
---@field public closed boolean
|
||||
---@field public on_close_cb fun()|nil
|
||||
local buffer = {}
|
||||
|
||||
---Create new buffer object
|
||||
---@param bufnr number
|
||||
---@param length number
|
||||
---@param pattern string
|
||||
---@return cmp_buffer.Buffer
|
||||
function buffer.new(bufnr, length, pattern)
|
||||
local self = setmetatable({}, { __index = buffer })
|
||||
self.bufnr = bufnr
|
||||
self.regex = vim.regex(pattern)
|
||||
self.length = length
|
||||
self.pattern = pattern
|
||||
self.indexing_chunk_size = 1000
|
||||
self.indexing_interval = 200
|
||||
self.timer = nil
|
||||
self.lines_count = 0
|
||||
self.lines_words = {}
|
||||
self.closed = false
|
||||
self.on_close_cb = nil
|
||||
return self
|
||||
end
|
||||
|
||||
---Close buffer
|
||||
function buffer.close(self)
|
||||
self.closed = true
|
||||
self:stop_indexing_timer()
|
||||
self.lines_count = 0
|
||||
self.lines_words = {}
|
||||
if self.on_close_cb then
|
||||
self.on_close_cb()
|
||||
end
|
||||
end
|
||||
|
||||
function buffer.stop_indexing_timer(self)
|
||||
if self.timer and not self.timer:is_closing() then
|
||||
self.timer:stop()
|
||||
self.timer:close()
|
||||
end
|
||||
self.timer = nil
|
||||
end
|
||||
|
||||
---Indexing buffer
|
||||
function buffer.index(self)
|
||||
self.lines_count = vim.api.nvim_buf_line_count(self.bufnr)
|
||||
for i = 1, self.lines_count do
|
||||
self.lines_words[i] = {}
|
||||
end
|
||||
|
||||
self:index_range_async(0, self.lines_count)
|
||||
end
|
||||
|
||||
function buffer.index_range(self, range_start, range_end)
|
||||
vim.api.nvim_buf_call(self.bufnr, function()
|
||||
local lines = vim.api.nvim_buf_get_lines(self.bufnr, range_start, range_end, true)
|
||||
for i, line in ipairs(lines) do
|
||||
self:index_line(range_start + i, line)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function buffer.index_range_async(self, range_start, range_end)
|
||||
local chunk_start = range_start
|
||||
|
||||
local lines = vim.api.nvim_buf_get_lines(self.bufnr, range_start, range_end, true)
|
||||
|
||||
self.timer = vim.loop.new_timer()
|
||||
self.timer:start(
|
||||
0,
|
||||
self.indexing_interval,
|
||||
vim.schedule_wrap(function()
|
||||
if self.closed then
|
||||
return
|
||||
end
|
||||
|
||||
local chunk_end = math.min(chunk_start + self.indexing_chunk_size, range_end)
|
||||
vim.api.nvim_buf_call(self.bufnr, function()
|
||||
for linenr = chunk_start + 1, chunk_end do
|
||||
self:index_line(linenr, lines[linenr])
|
||||
end
|
||||
end)
|
||||
chunk_start = chunk_end
|
||||
|
||||
if chunk_end >= range_end then
|
||||
self:stop_indexing_timer()
|
||||
end
|
||||
end)
|
||||
)
|
||||
end
|
||||
|
||||
--- watch
|
||||
function buffer.watch(self)
|
||||
-- NOTE: As far as I know, indexing in watching can't be done asynchronously
|
||||
-- because even built-in commands generate multiple consequent `on_lines`
|
||||
-- events, and I'm not even mentioning plugins here. To get accurate results
|
||||
-- we would have to either re-index the entire file on throttled events (slow
|
||||
-- and looses the benefit of on_lines watching), or put the events in a
|
||||
-- queue, which would complicate the plugin a lot. Plus, most changes which
|
||||
-- trigger this event will be from regular editing, and so 99% of the time
|
||||
-- they will affect only 1-2 lines.
|
||||
vim.api.nvim_buf_attach(self.bufnr, false, {
|
||||
-- NOTE: line indexes are 0-based and the last line is not inclusive.
|
||||
on_lines = function(_, _, _, first_line, old_last_line, new_last_line, _, _, _)
|
||||
if self.closed then
|
||||
return true
|
||||
end
|
||||
|
||||
local delta = new_last_line - old_last_line
|
||||
local old_lines_count = self.lines_count
|
||||
local new_lines_count = old_lines_count + delta
|
||||
if new_lines_count == 0 then -- clear
|
||||
-- This branch protects against bugs after full-file deletion. If you
|
||||
-- do, for example, gdGG, the new_last_line of the event will be zero.
|
||||
-- Which is not true, a buffer always contains at least one empty line,
|
||||
-- only unloaded buffers contain zero lines.
|
||||
new_lines_count = 1
|
||||
for i = old_lines_count, 2, -1 do
|
||||
self.lines_words[i] = nil
|
||||
end
|
||||
self.lines_words[1] = {}
|
||||
elseif delta > 0 then -- append
|
||||
-- Explicitly reserve more slots in the array part of the lines table,
|
||||
-- all of them will be filled in the next loop, but in reverse order
|
||||
-- (which is why I am concerned about preallocation). Why is there no
|
||||
-- built-in function to do this in Lua???
|
||||
for i = old_lines_count + 1, new_lines_count do
|
||||
self.lines_words[i] = vim.NIL
|
||||
end
|
||||
-- Move forwards the unchanged elements in the tail part.
|
||||
for i = old_lines_count, old_last_line + 1, -1 do
|
||||
self.lines_words[i + delta] = self.lines_words[i]
|
||||
end
|
||||
-- Fill in new tables for the added lines.
|
||||
for i = old_last_line + 1, new_last_line do
|
||||
self.lines_words[i] = {}
|
||||
end
|
||||
elseif delta < 0 then -- remove
|
||||
-- Move backwards the unchanged elements in the tail part.
|
||||
for i = old_last_line + 1, old_lines_count do
|
||||
self.lines_words[i + delta] = self.lines_words[i]
|
||||
end
|
||||
-- Remove (already copied) tables from the end, in reverse order, so
|
||||
-- that we don't make holes in the lines table.
|
||||
for i = old_lines_count, new_lines_count + 1, -1 do
|
||||
self.lines_words[i] = nil
|
||||
end
|
||||
end
|
||||
self.lines_count = new_lines_count
|
||||
|
||||
-- replace lines
|
||||
self:index_range(first_line, new_last_line)
|
||||
end,
|
||||
|
||||
on_reload = function(_, _)
|
||||
if self.closed then
|
||||
return true
|
||||
end
|
||||
|
||||
-- The logic for adjusting lines list on buffer reloads is much simpler
|
||||
-- because tables of all lines can be assumed to be fresh.
|
||||
local new_lines_count = vim.api.nvim_buf_line_count(self.bufnr)
|
||||
if new_lines_count > self.lines_count then -- append
|
||||
for i = self.lines_count + 1, new_lines_count do
|
||||
self.lines_words[i] = {}
|
||||
end
|
||||
elseif new_lines_count < self.lines_count then -- remove
|
||||
for i = self.lines_count, new_lines_count + 1, -1 do
|
||||
self.lines_words[i] = nil
|
||||
end
|
||||
end
|
||||
self.lines_count = new_lines_count
|
||||
|
||||
self:index_range(0, self.lines_count)
|
||||
end,
|
||||
|
||||
on_detach = function(_, _)
|
||||
if self.closed then
|
||||
return true
|
||||
end
|
||||
self:close()
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---@param linenr number
|
||||
---@param line string
|
||||
function buffer.index_line(self, linenr, line)
|
||||
local words = self.lines_words[linenr]
|
||||
for k, _ in ipairs(words) do
|
||||
words[k] = nil
|
||||
end
|
||||
local word_i = 1
|
||||
|
||||
local remaining = line
|
||||
while #remaining > 0 do
|
||||
-- NOTE: Both start and end indexes here are 0-based (unlike Lua strings),
|
||||
-- and the end index is not inclusive.
|
||||
local match_start, match_end = self.regex:match_str(remaining)
|
||||
if match_start and match_end then
|
||||
local word = remaining:sub(match_start + 1, match_end)
|
||||
if #word >= self.length then
|
||||
words[word_i] = word
|
||||
word_i = word_i + 1
|
||||
end
|
||||
remaining = remaining:sub(match_end + 1)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- get_words
|
||||
function buffer.get_words(self)
|
||||
local words = {}
|
||||
for _, line in ipairs(self.lines_words) do
|
||||
for _, w in ipairs(line) do
|
||||
table.insert(words, w)
|
||||
end
|
||||
end
|
||||
return words
|
||||
end
|
||||
|
||||
return buffer
|
91
bundle/cmp-buffer/lua/cmp_buffer/init.lua
Normal file
91
bundle/cmp-buffer/lua/cmp_buffer/init.lua
Normal file
@ -0,0 +1,91 @@
|
||||
local buffer = require('cmp_buffer.buffer')
|
||||
|
||||
local defaults = {
|
||||
keyword_length = 3,
|
||||
keyword_pattern = [[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%([\-]\w*\)*\)]],
|
||||
get_bufnrs = function()
|
||||
return { vim.api.nvim_get_current_buf() }
|
||||
end,
|
||||
}
|
||||
|
||||
local source = {}
|
||||
|
||||
source.new = function()
|
||||
local self = setmetatable({}, { __index = source })
|
||||
self.buffers = {}
|
||||
return self
|
||||
end
|
||||
|
||||
source.get_keyword_pattern = function(_, params)
|
||||
params.option = vim.tbl_deep_extend('keep', params.option, defaults)
|
||||
vim.validate({
|
||||
keyword_length = { params.option.keyword_length, 'number', '`opts.keyword_length` must be `number`' },
|
||||
keyword_pattern = { params.option.keyword_pattern, 'string', '`opts.keyword_pattern` must be `string`' },
|
||||
get_bufnrs = { params.option.get_bufnrs, 'function', '`opts.get_bufnrs` must be `function`' },
|
||||
})
|
||||
return params.option.keyword_pattern
|
||||
end
|
||||
|
||||
source.complete = function(self, params, callback)
|
||||
params.option = vim.tbl_deep_extend('keep', params.option, defaults)
|
||||
vim.validate({
|
||||
keyword_pattern = { params.option.keyword_pattern, 'string', '`opts.keyword_pattern` must be `string`' },
|
||||
get_bufnrs = { params.option.get_bufnrs, 'function', '`opts.get_bufnrs` must be `function`' },
|
||||
})
|
||||
|
||||
local processing = false
|
||||
local bufs = self:_get_buffers(params)
|
||||
for _, buf in ipairs(bufs) do
|
||||
if buf.timer then
|
||||
processing = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
vim.defer_fn(function()
|
||||
local input = string.sub(params.context.cursor_before_line, params.offset)
|
||||
local items = {}
|
||||
local words = {}
|
||||
for _, buf in ipairs(bufs) do
|
||||
for _, word in ipairs(buf:get_words()) do
|
||||
if not words[word] and input ~= word then
|
||||
words[word] = true
|
||||
table.insert(items, {
|
||||
label = word,
|
||||
dup = 0,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
callback({
|
||||
items = items,
|
||||
isIncomplete = processing,
|
||||
})
|
||||
end, processing and 100 or 0)
|
||||
end
|
||||
|
||||
--- _get_bufs
|
||||
source._get_buffers = function(self, params)
|
||||
local buffers = {}
|
||||
for _, bufnr in ipairs(params.option.get_bufnrs()) do
|
||||
if not self.buffers[bufnr] then
|
||||
local new_buf = buffer.new(
|
||||
bufnr,
|
||||
params.option.keyword_length,
|
||||
params.option.keyword_pattern
|
||||
)
|
||||
new_buf.on_close_cb = function()
|
||||
self.buffers[bufnr] = nil
|
||||
end
|
||||
new_buf:index()
|
||||
new_buf:watch()
|
||||
self.buffers[bufnr] = new_buf
|
||||
end
|
||||
table.insert(buffers, self.buffers[bufnr])
|
||||
end
|
||||
|
||||
return buffers
|
||||
end
|
||||
|
||||
return source
|
13
bundle/cmp-cmdline/README.md
Normal file
13
bundle/cmp-cmdline/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# cmp-cmdline
|
||||
|
||||
nvim-cmp source for vim's cmdline.
|
||||
|
||||
# Setup
|
||||
|
||||
```lua
|
||||
require'cmp'.setup.cmdline(':', {
|
||||
sources = {
|
||||
{ name = 'cmdline' }
|
||||
}
|
||||
})
|
||||
```
|
1
bundle/cmp-cmdline/after/plugin/cmp_cmdline.lua
Normal file
1
bundle/cmp-cmdline/after/plugin/cmp_cmdline.lua
Normal file
@ -0,0 +1 @@
|
||||
require('cmp').register_source('cmdline', require('cmp_cmdline').new())
|
135
bundle/cmp-cmdline/lua/cmp_cmdline/init.lua
Normal file
135
bundle/cmp-cmdline/lua/cmp_cmdline/init.lua
Normal file
@ -0,0 +1,135 @@
|
||||
local cmp = require('cmp')
|
||||
|
||||
local definitions = {
|
||||
{
|
||||
ctype = 'customlist',
|
||||
regex = [=[[^[:blank:]]*$]=],
|
||||
kind = cmp.lsp.CompletionItemKind.Variable,
|
||||
fallback = true,
|
||||
isIncomplete = false,
|
||||
exec = function(arglead, cmdline, curpos)
|
||||
local name = cmdline:match([=[^[ <'>]*(%a*)]=])
|
||||
if not name then
|
||||
return {}
|
||||
end
|
||||
for name_, option in pairs(vim.api.nvim_get_commands({ builtin = false })) do
|
||||
if name_ == name then
|
||||
if vim.tbl_contains({ 'customlist', 'custom' }, option.complete) then
|
||||
local ok, items = pcall(function()
|
||||
local func = string.gsub(option.complete_arg, 's:', ('<SNR>%d_'):format(option.script_id))
|
||||
return vim.fn.eval(('%s("%s", "%s", "%s")'):format(
|
||||
func,
|
||||
vim.fn.escape(arglead, '"'),
|
||||
vim.fn.escape(cmdline, '"'),
|
||||
vim.fn.escape(curpos, '"')
|
||||
))
|
||||
end)
|
||||
if not ok then
|
||||
return {}
|
||||
end
|
||||
if type(items) == 'string' then
|
||||
return vim.split(items, '\n')
|
||||
elseif type(items) == 'table' then
|
||||
return items
|
||||
end
|
||||
return {}
|
||||
end
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
},
|
||||
{
|
||||
ctype = 'cmdline',
|
||||
regex = [=[^[^!].*]=],
|
||||
kind = cmp.lsp.CompletionItemKind.Variable,
|
||||
isIncomplete = true,
|
||||
exec = function(_, cmdline, _)
|
||||
return vim.fn.getcompletion(cmdline, 'cmdline')
|
||||
end
|
||||
},
|
||||
}
|
||||
|
||||
local source = {}
|
||||
|
||||
source.new = function()
|
||||
return setmetatable({
|
||||
before_line = '',
|
||||
offset = -1,
|
||||
ctype = '',
|
||||
items = {},
|
||||
}, { __index = source })
|
||||
end
|
||||
|
||||
source.get_keyword_pattern = function()
|
||||
return [=[[[:keyword:]-]*]=]
|
||||
end
|
||||
|
||||
source.get_trigger_characters = function()
|
||||
return { ' ', '.' }
|
||||
end
|
||||
|
||||
source.is_available = function()
|
||||
return vim.api.nvim_get_mode().mode == 'c'
|
||||
end
|
||||
|
||||
source.complete = function(self, params, callback)
|
||||
local offset = 0
|
||||
local ctype = ''
|
||||
local items = {}
|
||||
local kind = ''
|
||||
local isIncomplete = false
|
||||
for _, def in ipairs(definitions) do
|
||||
local s, e = vim.regex(def.regex):match_str(params.context.cursor_before_line)
|
||||
if s and e then
|
||||
offset = s
|
||||
ctype = def.type
|
||||
items = def.exec(
|
||||
string.sub(params.context.cursor_before_line, s + 1),
|
||||
params.context.cursor_before_line,
|
||||
params.context.cursor.col
|
||||
)
|
||||
kind = def.kind
|
||||
isIncomplete = def.isIncomplete
|
||||
if not (#items == 0 and def.fallback) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local labels = {}
|
||||
items = vim.tbl_map(function(item)
|
||||
if type(item) == 'string' then
|
||||
labels[item] = true
|
||||
return { label = item, kind = kind }
|
||||
end
|
||||
labels[item.word] = true
|
||||
return { label = item.word, kind = kind }
|
||||
end, items)
|
||||
|
||||
local match_prefix = false
|
||||
if #params.context.cursor_before_line > #self.before_line then
|
||||
match_prefix = string.find(params.context.cursor_before_line, self.before_line, 1, true) == 1
|
||||
elseif #params.context.cursor_before_line < #self.before_line then
|
||||
match_prefix = string.find(self.before_line, params.context.cursor_before_line, 1, true) == 1
|
||||
end
|
||||
if match_prefix and self.offset == offset and self.ctype == ctype then
|
||||
for _, item in ipairs(self.items) do
|
||||
if not labels[item.label] then
|
||||
table.insert(items, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.before_line = params.context.cursor_before_line
|
||||
self.offset = offset
|
||||
self.ctype = ctype
|
||||
self.items = items
|
||||
|
||||
callback({
|
||||
isIncomplete = isIncomplete,
|
||||
items = items,
|
||||
})
|
||||
end
|
||||
|
||||
return source
|
||||
|
21
bundle/cmp-nvim-lsp/LICENSE
Normal file
21
bundle/cmp-nvim-lsp/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 hrsh7th
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
24
bundle/cmp-nvim-lsp/README.md
Normal file
24
bundle/cmp-nvim-lsp/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# cmp-nvim-lsp
|
||||
|
||||
nvim-cmp source for neovim builtin LSP client
|
||||
|
||||
# Setup
|
||||
|
||||
```lua
|
||||
|
||||
require'cmp'.setup {
|
||||
sources = {
|
||||
{ name = 'nvim_lsp' }
|
||||
}
|
||||
}
|
||||
|
||||
-- The nvim-cmp almost supports LSP's capabilities so You should advertise it to LSP servers..
|
||||
local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||
capabilities = require('cmp_nvim_lsp').update_capabilities(capabilities)
|
||||
|
||||
-- The following example advertise capabilities to `clangd`.
|
||||
require'lspconfig'.clangd.setup {
|
||||
capabilities = capabilities,
|
||||
}
|
||||
```
|
||||
|
1
bundle/cmp-nvim-lsp/after/plugin/cmp_nvim_lsp.lua
Normal file
1
bundle/cmp-nvim-lsp/after/plugin/cmp_nvim_lsp.lua
Normal file
@ -0,0 +1 @@
|
||||
require('cmp_nvim_lsp').setup()
|
84
bundle/cmp-nvim-lsp/lua/cmp_nvim_lsp/init.lua
Normal file
84
bundle/cmp-nvim-lsp/lua/cmp_nvim_lsp/init.lua
Normal file
@ -0,0 +1,84 @@
|
||||
local source = require('cmp_nvim_lsp.source')
|
||||
|
||||
local M = {}
|
||||
|
||||
---Registered client and source mapping.
|
||||
M.client_source_map = {}
|
||||
|
||||
---Setup cmp-nvim-lsp source.
|
||||
M.setup = function()
|
||||
vim.cmd([[
|
||||
augroup cmp_nvim_lsp
|
||||
autocmd!
|
||||
autocmd InsertEnter * lua require'cmp_nvim_lsp'._on_insert_enter()
|
||||
augroup END
|
||||
]])
|
||||
end
|
||||
|
||||
local if_nil = function(val, default)
|
||||
if val == nil then return default end
|
||||
return val
|
||||
end
|
||||
|
||||
M.update_capabilities = function(capabilities, override)
|
||||
override = override or {}
|
||||
|
||||
local completionItem = capabilities.textDocument.completion.completionItem
|
||||
|
||||
completionItem.snippetSupport = if_nil(override.snippetSupport, true)
|
||||
completionItem.preselectSupport = if_nil(override.preselectSupport, true)
|
||||
completionItem.insertReplaceSupport = if_nil(override.insertReplaceSupport, true)
|
||||
completionItem.labelDetailsSupport = if_nil(override.labelDetailsSupport, true)
|
||||
completionItem.deprecatedSupport = if_nil(override.deprecatedSupport, true)
|
||||
completionItem.commitCharactersSupport = if_nil(override.commitCharactersSupport, true)
|
||||
completionItem.tagSupport = if_nil(override.tagSupport, { valueSet = { 1 } })
|
||||
completionItem.resolveSupport = if_nil(override.resolveSupport, {
|
||||
properties = {
|
||||
'documentation',
|
||||
'detail',
|
||||
'additionalTextEdits',
|
||||
}
|
||||
})
|
||||
|
||||
return capabilities
|
||||
end
|
||||
|
||||
---Refresh sources on InsertEnter.
|
||||
M._on_insert_enter = function()
|
||||
local cmp = require('cmp')
|
||||
|
||||
local allowed_clients = {}
|
||||
|
||||
-- register all active clients.
|
||||
for _, client in ipairs(vim.lsp.get_active_clients()) do
|
||||
allowed_clients[client.id] = client
|
||||
if not M.client_source_map[client.id] then
|
||||
local s = source.new(client)
|
||||
if s:is_available() then
|
||||
M.client_source_map[client.id] = cmp.register_source('nvim_lsp', s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- register all buffer clients (early register before activation)
|
||||
for _, client in ipairs(vim.lsp.buf_get_clients(0)) do
|
||||
allowed_clients[client.id] = client
|
||||
if not M.client_source_map[client.id] then
|
||||
local s = source.new(client)
|
||||
if s:is_available() then
|
||||
M.client_source_map[client.id] = cmp.register_source('nvim_lsp', s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- unregister stopped/detached clients.
|
||||
for client_id, source_id in pairs(M.client_source_map) do
|
||||
if not allowed_clients[client_id] or allowed_clients[client_id]:is_stopped() then
|
||||
cmp.unregister_source(source_id)
|
||||
M.client_source_map[client_id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
117
bundle/cmp-nvim-lsp/lua/cmp_nvim_lsp/source.lua
Normal file
117
bundle/cmp-nvim-lsp/lua/cmp_nvim_lsp/source.lua
Normal file
@ -0,0 +1,117 @@
|
||||
local source = {}
|
||||
|
||||
source.new = function(client)
|
||||
local self = setmetatable({}, { __index = source })
|
||||
self.client = client
|
||||
self.request_ids = {}
|
||||
return self
|
||||
end
|
||||
|
||||
source.get_debug_name = function(self)
|
||||
return table.concat({ 'nvim_lsp', self.client.name }, ':')
|
||||
end
|
||||
|
||||
source.is_available = function(self)
|
||||
-- client is stopped.
|
||||
if self.client.is_stopped() then
|
||||
return false
|
||||
end
|
||||
|
||||
-- client is not attached to current buffer.
|
||||
if not vim.lsp.buf_get_clients(vim.api.nvim_get_current_buf())[self.client.id] then
|
||||
return false
|
||||
end
|
||||
|
||||
-- client has no completion capability.
|
||||
if not self:_get(self.client.server_capabilities, { 'completionProvider' }) then
|
||||
return false
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
source.get_trigger_characters = function(self)
|
||||
return self:_get(self.client.server_capabilities, { 'completionProvider', 'triggerCharacters' }) or {}
|
||||
end
|
||||
|
||||
source.complete = function(self, request, callback)
|
||||
local params = vim.lsp.util.make_position_params()
|
||||
params.context = {}
|
||||
params.context.triggerKind = request.completion_context.triggerKind
|
||||
params.context.triggerCharacter = request.completion_context.triggerCharacter
|
||||
|
||||
self:_request('textDocument/completion', params, function(_, response)
|
||||
callback(response)
|
||||
end)
|
||||
end
|
||||
|
||||
source.resolve = function(self, completion_item, callback)
|
||||
-- client is stopped.
|
||||
if self.client.is_stopped() then
|
||||
return callback()
|
||||
end
|
||||
|
||||
-- client has no completion capability.
|
||||
if not self:_get(self.client.server_capabilities, { 'completionProvider', 'resolveProvider' }) then
|
||||
return callback()
|
||||
end
|
||||
|
||||
self:_request('completionItem/resolve', completion_item, function(_, response)
|
||||
callback(response or completion_item)
|
||||
end)
|
||||
end
|
||||
|
||||
source.execute = function(self, completion_item, callback)
|
||||
-- client is stopped.
|
||||
if self.client.is_stopped() then
|
||||
return callback()
|
||||
end
|
||||
|
||||
-- completion_item has no command.
|
||||
if not completion_item.command then
|
||||
return callback()
|
||||
end
|
||||
|
||||
self:_request('workspace/executeCommand', completion_item.command, function(_, _)
|
||||
callback()
|
||||
end)
|
||||
end
|
||||
|
||||
source._get = function(_, root, paths)
|
||||
local c = root
|
||||
for _, path in ipairs(paths) do
|
||||
c = c[path]
|
||||
if not c then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
source._request = function(self, method, params, callback)
|
||||
if self.request_ids[method] ~= nil then
|
||||
self.client.cancel_request(self.request_ids[method])
|
||||
self.request_ids[method] = nil
|
||||
end
|
||||
local _, request_id
|
||||
_, request_id = self.client.request(method, params, function(arg1, arg2, arg3)
|
||||
if self.request_ids[method] ~= request_id then
|
||||
return
|
||||
end
|
||||
self.request_ids[method] = nil
|
||||
|
||||
-- Text changed, retry
|
||||
if arg1 and arg1.code == -32801 then
|
||||
self:_request(method, params, callback)
|
||||
return
|
||||
end
|
||||
|
||||
if method == arg2 then
|
||||
callback(arg1, arg3) -- old signature
|
||||
else
|
||||
callback(arg1, arg2) -- new signature
|
||||
end
|
||||
end)
|
||||
self.request_ids[method] = request_id
|
||||
end
|
||||
|
||||
return source
|
21
bundle/cmp-path/LICENSE
Normal file
21
bundle/cmp-path/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 hrsh7th
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
15
bundle/cmp-path/README.md
Normal file
15
bundle/cmp-path/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# cmp-path
|
||||
|
||||
nvim-cmp source for filesystem paths.
|
||||
|
||||
# Setup
|
||||
|
||||
```lua
|
||||
require'cmp'.setup {
|
||||
sources = {
|
||||
{ name = 'path' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
1
bundle/cmp-path/after/plugin/cmp_path.lua
Normal file
1
bundle/cmp-path/after/plugin/cmp_path.lua
Normal file
@ -0,0 +1 @@
|
||||
require('cmp').register_source('path', require('cmp_path').new())
|
207
bundle/cmp-path/lua/cmp_path/init.lua
Normal file
207
bundle/cmp-path/lua/cmp_path/init.lua
Normal file
@ -0,0 +1,207 @@
|
||||
local cmp = require'cmp'
|
||||
|
||||
local NAME_REGEX = '\\%([^/\\\\:\\*?<>\'"`\\|]\\)'
|
||||
local PATH_REGEX = vim.regex(([[\%(/PAT\+\)*/\zePAT*$]]):gsub('PAT', NAME_REGEX))
|
||||
|
||||
local source = {}
|
||||
|
||||
local defaults = {
|
||||
max_lines = 20,
|
||||
}
|
||||
|
||||
source.new = function()
|
||||
return setmetatable({}, { __index = source })
|
||||
end
|
||||
|
||||
source.get_trigger_characters = function()
|
||||
return { '/', '.' }
|
||||
end
|
||||
|
||||
source.get_keyword_pattern = function()
|
||||
return NAME_REGEX .. '*'
|
||||
end
|
||||
|
||||
source.complete = function(self, params, callback)
|
||||
local dirname = self:_dirname(params)
|
||||
if not dirname then
|
||||
return callback()
|
||||
end
|
||||
|
||||
local stat = self:_stat(dirname)
|
||||
if not stat then
|
||||
return callback()
|
||||
end
|
||||
|
||||
self:_candidates(params, dirname, params.offset, function(err, candidates)
|
||||
if err then
|
||||
return callback()
|
||||
end
|
||||
callback(candidates)
|
||||
end)
|
||||
end
|
||||
|
||||
source._dirname = function(self, params)
|
||||
local s = PATH_REGEX:match_str(params.context.cursor_before_line)
|
||||
if not s then
|
||||
return nil
|
||||
end
|
||||
|
||||
local dirname = string.gsub(string.sub(params.context.cursor_before_line, s + 2), '%a*$', '') -- exclude '/'
|
||||
local prefix = string.sub(params.context.cursor_before_line, 1, s + 1) -- include '/'
|
||||
|
||||
local buf_dirname = vim.fn.expand(('#%d:p:h'):format(params.context.bufnr))
|
||||
if vim.api.nvim_get_mode().mode == 'c' then
|
||||
buf_dirname = vim.fn.getcwd()
|
||||
end
|
||||
if prefix:match('%.%./$') then
|
||||
return vim.fn.resolve(buf_dirname .. '/../' .. dirname)
|
||||
end
|
||||
if prefix:match('%./$') then
|
||||
return vim.fn.resolve(buf_dirname .. '/' .. dirname)
|
||||
end
|
||||
if prefix:match('~/$') then
|
||||
return vim.fn.resolve(vim.fn.expand('~') .. '/' .. dirname)
|
||||
end
|
||||
local env_var_name = prefix:match('%$([%a_]+)/$')
|
||||
if env_var_name then
|
||||
local env_var_value = vim.fn.getenv(env_var_name)
|
||||
if env_var_value ~= vim.NIL then
|
||||
return vim.fn.resolve(env_var_value .. '/' .. dirname)
|
||||
end
|
||||
end
|
||||
if prefix:match('/$') then
|
||||
local accept = true
|
||||
-- Ignore URL components
|
||||
accept = accept and not prefix:match('%a/$')
|
||||
-- Ignore URL scheme
|
||||
accept = accept and not prefix:match('%a+:/$') and not prefix:match('%a+://$')
|
||||
-- Ignore HTML closing tags
|
||||
accept = accept and not prefix:match('</$')
|
||||
-- Ignore math calculation
|
||||
accept = accept and not prefix:match('[%d%)]%s*/$')
|
||||
-- Ignore / comment
|
||||
accept = accept and (not prefix:match('^[%s/]*$') or not self:_is_slash_comment())
|
||||
if accept then
|
||||
return vim.fn.resolve('/' .. dirname)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
source._stat = function(_, path)
|
||||
local stat = vim.loop.fs_stat(path)
|
||||
if stat then
|
||||
return stat
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function lines_from(file, count)
|
||||
local bfile = assert(io.open(file, 'rb'))
|
||||
local first_k = bfile:read(1024)
|
||||
if first_k:find('\0') then
|
||||
return {'binary file'}
|
||||
end
|
||||
local lines = {'```'}
|
||||
for line in first_k:gmatch("[^\r\n]+") do
|
||||
lines[#lines + 1] = line
|
||||
if count ~= nil and #lines >= count then
|
||||
break
|
||||
end
|
||||
end
|
||||
lines[#lines + 1] = '```'
|
||||
return lines
|
||||
end
|
||||
|
||||
local function try_get_lines(file, count)
|
||||
status, ret = pcall(lines_from, file, count)
|
||||
if status then
|
||||
return ret
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
source._candidates = function(_, params, dirname, offset, callback)
|
||||
local fs, err = vim.loop.fs_scandir(dirname)
|
||||
if err then
|
||||
return callback(err, nil)
|
||||
end
|
||||
|
||||
local items = {}
|
||||
|
||||
|
||||
local include_hidden = string.sub(params.context.cursor_before_line, offset, offset) == '.'
|
||||
while true do
|
||||
local name, type, e = vim.loop.fs_scandir_next(fs)
|
||||
if e then
|
||||
return callback(type, nil)
|
||||
end
|
||||
if not name then
|
||||
break
|
||||
end
|
||||
|
||||
local accept = false
|
||||
accept = accept or include_hidden
|
||||
accept = accept or name:sub(1, 1) ~= '.'
|
||||
|
||||
-- Create items
|
||||
if accept then
|
||||
if type == 'directory' then
|
||||
table.insert(items, {
|
||||
word = name,
|
||||
label = name,
|
||||
insertText = name .. '/',
|
||||
kind = cmp.lsp.CompletionItemKind.Folder,
|
||||
})
|
||||
elseif type == 'link' then
|
||||
local stat = vim.loop.fs_stat(dirname .. '/' .. name)
|
||||
if stat then
|
||||
if stat.type == 'directory' then
|
||||
table.insert(items, {
|
||||
word = name,
|
||||
label = name,
|
||||
insertText = name .. '/',
|
||||
kind = cmp.lsp.CompletionItemKind.Folder,
|
||||
})
|
||||
else
|
||||
table.insert(items, {
|
||||
label = name,
|
||||
filterText = name,
|
||||
insertText = name,
|
||||
kind = cmp.lsp.CompletionItemKind.File,
|
||||
data = {path = dirname .. '/' .. name},
|
||||
})
|
||||
end
|
||||
end
|
||||
elseif type == 'file' then
|
||||
table.insert(items, {
|
||||
label = name,
|
||||
filterText = name,
|
||||
insertText = name,
|
||||
kind = cmp.lsp.CompletionItemKind.File,
|
||||
data = {path = dirname .. '/' .. name},
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
callback(nil, items)
|
||||
end
|
||||
|
||||
source._is_slash_comment = function(_)
|
||||
local commentstring = vim.bo.commentstring or ''
|
||||
local no_filetype = vim.bo.filetype == ''
|
||||
local is_slash_comment = false
|
||||
is_slash_comment = is_slash_comment or commentstring:match('/%*')
|
||||
is_slash_comment = is_slash_comment or commentstring:match('//')
|
||||
return is_slash_comment and not no_filetype
|
||||
end
|
||||
|
||||
function source:resolve(completion_item, callback)
|
||||
if completion_item.kind == cmp.lsp.CompletionItemKind.File then
|
||||
completion_item.documentation = try_get_lines(completion_item.data.path, defaults.max_lines)
|
||||
end
|
||||
callback(completion_item)
|
||||
end
|
||||
|
||||
return source
|
21
bundle/lspkind-nvim/LICENSE
Normal file
21
bundle/lspkind-nvim/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Andrey Kuznetsov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
74
bundle/lspkind-nvim/README.md
Normal file
74
bundle/lspkind-nvim/README.md
Normal file
@ -0,0 +1,74 @@
|
||||
# lspkind-nvim
|
||||
|
||||
This tiny plugin adds vscode-like pictograms to neovim built-in lsp:
|
||||
|
||||
![Screenshot](https://github.com/onsails/lspkind-nvim/raw/images/images/screenshot.png "Screenshot")
|
||||
<sup>[nvim-compe](https://github.com/hrsh7th/nvim-compe), [vim-vsnip](https://github.com/hrsh7th/vim-vsnip), [vim-vsnip-integ](https://github.com/hrsh7th/vim-vsnip-integ), [jellybeans-nvim](https://github.com/metalelf0/jellybeans-nvim)</sup>
|
||||
|
||||
## Configuration
|
||||
|
||||
### Option 1: vanilla Neovim LSP
|
||||
|
||||
Wherever you configure lsp put the following lua command:
|
||||
|
||||
```lua
|
||||
require('lspkind').init({
|
||||
-- enables text annotations
|
||||
--
|
||||
-- default: true
|
||||
with_text = true,
|
||||
|
||||
-- default symbol map
|
||||
-- can be either 'default' (requires nerd-fonts font) or
|
||||
-- 'codicons' for codicon preset (requires vscode-codicons font)
|
||||
--
|
||||
-- default: 'default'
|
||||
preset = 'codicons',
|
||||
|
||||
-- override preset symbols
|
||||
--
|
||||
-- default: {}
|
||||
symbol_map = {
|
||||
Text = "",
|
||||
Method = "",
|
||||
Function = "",
|
||||
Constructor = "",
|
||||
Field = "ﰠ",
|
||||
Variable = "",
|
||||
Class = "ﴯ",
|
||||
Interface = "",
|
||||
Module = "",
|
||||
Property = "ﰠ",
|
||||
Unit = "塞",
|
||||
Value = "",
|
||||
Enum = "",
|
||||
Keyword = "",
|
||||
Snippet = "",
|
||||
Color = "",
|
||||
File = "",
|
||||
Reference = "",
|
||||
Folder = "",
|
||||
EnumMember = "",
|
||||
Constant = "",
|
||||
Struct = "פּ",
|
||||
Event = "",
|
||||
Operator = "",
|
||||
TypeParameter = ""
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Option 2: [nvim-cmp](https://github.com/hrsh7th/nvim-cmp)
|
||||
|
||||
```lua
|
||||
local lspkind = require('lspkind')
|
||||
cmp.setup {
|
||||
formatting = {
|
||||
format = lspkind.cmp_format({with_text = false, maxwidth = 50})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Related LSP plugins
|
||||
|
||||
[diaglist.nvim](https://github.com/onsails/diaglist.nvim) – live render workspace diagnostics in quickfix with current buf errors on top, buffer diagnostics in loclist
|
143
bundle/lspkind-nvim/lua/lspkind/init.lua
Normal file
143
bundle/lspkind-nvim/lua/lspkind/init.lua
Normal file
@ -0,0 +1,143 @@
|
||||
local lspkind = {}
|
||||
local fmt = string.format
|
||||
|
||||
local kind_presets = {
|
||||
default = {
|
||||
-- if you change or add symbol here
|
||||
-- replace corresponding line in readme
|
||||
Text = "",
|
||||
Method = "",
|
||||
Function = "",
|
||||
Constructor = "",
|
||||
Field = "ﰠ",
|
||||
Variable = "",
|
||||
Class = "ﴯ",
|
||||
Interface = "",
|
||||
Module = "",
|
||||
Property = "ﰠ",
|
||||
Unit = "塞",
|
||||
Value = "",
|
||||
Enum = "",
|
||||
Keyword = "",
|
||||
Snippet = "",
|
||||
Color = "",
|
||||
File = "",
|
||||
Reference = "",
|
||||
Folder = "",
|
||||
EnumMember = "",
|
||||
Constant = "",
|
||||
Struct = "פּ",
|
||||
Event = "",
|
||||
Operator = "",
|
||||
TypeParameter = ""
|
||||
},
|
||||
codicons = {
|
||||
Text = "",
|
||||
Method = "",
|
||||
Function = "",
|
||||
Constructor = "",
|
||||
Field = "",
|
||||
Variable = "",
|
||||
Class = "",
|
||||
Interface = "",
|
||||
Module = "",
|
||||
Property = "",
|
||||
Unit = "",
|
||||
Value = "",
|
||||
Enum = "",
|
||||
Keyword = "",
|
||||
Snippet = "",
|
||||
Color = "",
|
||||
File = "",
|
||||
Reference = "",
|
||||
Folder = "",
|
||||
EnumMember = "",
|
||||
Constant = "",
|
||||
Struct = "",
|
||||
Event = "",
|
||||
Operator = "",
|
||||
TypeParameter = "",
|
||||
},
|
||||
}
|
||||
|
||||
local kind_order = {
|
||||
'Text', 'Method', 'Function', 'Constructor', 'Field', 'Variable', 'Class', 'Interface', 'Module',
|
||||
'Property', 'Unit', 'Value', 'Enum', 'Keyword', 'Snippet', 'Color', 'File', 'Reference', 'Folder',
|
||||
'EnumMember', 'Constant', 'Struct', 'Event', 'Operator', 'TypeParameter'
|
||||
}
|
||||
local kind_len = 25
|
||||
|
||||
-- default true
|
||||
local function opt_with_text(opts)
|
||||
return opts == nil or opts['with_text'] == nil or opts['with_text']
|
||||
end
|
||||
|
||||
-- default 'default'
|
||||
local function opt_preset(opts)
|
||||
local preset
|
||||
if opts == nil or opts['preset'] == nil then
|
||||
preset = 'default'
|
||||
else
|
||||
preset = opts['preset']
|
||||
end
|
||||
return preset
|
||||
end
|
||||
|
||||
function lspkind.init(opts)
|
||||
local preset = opt_preset(opts)
|
||||
|
||||
local symbol_map = kind_presets[preset]
|
||||
lspkind.symbol_map = (opts and opts['symbol_map'] and
|
||||
vim.tbl_extend('force', symbol_map, opts['symbol_map'])) or symbol_map
|
||||
|
||||
local symbols = {}
|
||||
local len = kind_len
|
||||
for i = 1, len do
|
||||
local name = kind_order[i]
|
||||
symbols[i] = lspkind.symbolic(name, opts)
|
||||
end
|
||||
|
||||
for k,v in pairs(symbols) do
|
||||
require('vim.lsp.protocol').CompletionItemKind[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
lspkind.presets = kind_presets
|
||||
lspkind.symbol_map = kind_presets.default
|
||||
|
||||
function lspkind.symbolic(kind, opts)
|
||||
local with_text = opt_with_text(opts)
|
||||
|
||||
local symbol = lspkind.symbol_map[kind]
|
||||
if with_text == true then
|
||||
symbol = symbol and (symbol .. ' ') or ''
|
||||
return fmt('%s%s', symbol, kind)
|
||||
else
|
||||
return symbol
|
||||
end
|
||||
end
|
||||
|
||||
function lspkind.cmp_format(opts)
|
||||
if opts == nil then
|
||||
opts = {}
|
||||
end
|
||||
if opts.preset or opts.symbol_map then
|
||||
lspkind.init(opts)
|
||||
end
|
||||
|
||||
return function(entry, vim_item)
|
||||
vim_item.kind = lspkind.symbolic(vim_item.kind, opts)
|
||||
|
||||
if opts.menu ~= nil then
|
||||
vim_item.menu = opts.menu[entry.source.name]
|
||||
end
|
||||
|
||||
if opts.maxwidth ~= nil then
|
||||
vim_item.abbr = string.sub(vim_item.abbr, 1, opts.maxwidth)
|
||||
end
|
||||
|
||||
return vim_item
|
||||
end
|
||||
end
|
||||
|
||||
return lspkind
|
9
bundle/nvim-cmp/.githooks/pre-commit
Normal file
9
bundle/nvim-cmp/.githooks/pre-commit
Normal file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
DIR="$(dirname $(dirname $( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )))"
|
||||
|
||||
cd $DIR
|
||||
make pre-commit
|
||||
for FILE in `git diff --staged --name-only`; do
|
||||
git add $FILE
|
||||
done
|
3
bundle/nvim-cmp/.github/FUNDING.yml
vendored
Normal file
3
bundle/nvim-cmp/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [hrsh7th]
|
31
bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
bundle/nvim-cmp/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
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**
|
50
bundle/nvim-cmp/.github/workflows/integration.yaml
vendored
Normal file
50
bundle/nvim-cmp/.github/workflows/integration.yaml
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
name: integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
default: true
|
||||
override: true
|
||||
|
||||
- name: Setup neovim
|
||||
uses: rhysd/action-setup-vim@v1
|
||||
with:
|
||||
neovim: true
|
||||
|
||||
- name: Setup lua
|
||||
uses: leafo/gh-actions-lua@v8
|
||||
with:
|
||||
luaVersion: "luajit-2.1.0-beta3"
|
||||
|
||||
- name: Setup luarocks
|
||||
uses: leafo/gh-actions-luarocks@v4
|
||||
|
||||
- name: Setup tools
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt install -y curl unzip --no-install-recommends
|
||||
bash ./utils/install_stylua.sh
|
||||
luarocks install luacheck
|
||||
luarocks install vusted
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: make integration
|
||||
|
1
bundle/nvim-cmp/.gitignore
vendored
Normal file
1
bundle/nvim-cmp/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
utils/stylua
|
2
bundle/nvim-cmp/.luacheckrc
Normal file
2
bundle/nvim-cmp/.luacheckrc
Normal file
@ -0,0 +1,2 @@
|
||||
globals = { 'vim', 'describe', 'it', 'before_each', 'after_each', 'assert', 'async' }
|
||||
max_line_length = false
|
21
bundle/nvim-cmp/LICENSE
Normal file
21
bundle/nvim-cmp/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 hrsh7th
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
24
bundle/nvim-cmp/Makefile
Normal file
24
bundle/nvim-cmp/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
stylua --config-path stylua.toml --glob 'lua/**/*.lua' -- lua
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
luacheck ./lua
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
vusted --output=gtest ./lua
|
||||
|
||||
.PHONY: pre-commit
|
||||
pre-commit:
|
||||
./utils/stylua --config-path stylua.toml --glob 'lua/**/*.lua' -- lua
|
||||
luacheck lua
|
||||
vusted lua
|
||||
|
||||
.PHONY: integration
|
||||
integration:
|
||||
./utils/stylua --config-path stylua.toml --check --glob 'lua/**/*.lua' -- lua
|
||||
luacheck lua
|
||||
vusted lua
|
||||
|
776
bundle/nvim-cmp/README.md
Normal file
776
bundle/nvim-cmp/README.md
Normal file
@ -0,0 +1,776 @@
|
||||
# nvim-cmp
|
||||
|
||||
A completion engine plugin for neovim written in Lua.
|
||||
Completion sources are installed from external repositories and "sourced".
|
||||
|
||||
<video src="https://user-images.githubusercontent.com/629908/139000570-3ac39587-a88b-43c6-b35e-207489719359.mp4" width="100%"></video>
|
||||
|
||||
Readme!
|
||||
====================
|
||||
|
||||
1. nvim-cmp's breaking changes are documented [here](https://github.com/hrsh7th/nvim-cmp/issues/231).
|
||||
2. This is my hobby project. You can support me via GitHub sponsors.
|
||||
3. Bug reports are welcome, but I might not fix if you don't provide a minimal reproduction configuration and steps.
|
||||
|
||||
|
||||
Concept
|
||||
====================
|
||||
|
||||
- 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
|
||||
====================
|
||||
|
||||
### Recommended Configuration
|
||||
|
||||
This example configuration uses `vim-plug` as the plugin manager.
|
||||
|
||||
```viml
|
||||
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 ultisnips users.
|
||||
" Plug 'SirVer/ultisnips'
|
||||
" Plug 'quangnguyen30192/cmp-nvim-ultisnips'
|
||||
|
||||
" For snippy users.
|
||||
" Plug 'dcampos/nvim-snippy'
|
||||
" Plug 'dcampos/cmp-snippy'
|
||||
|
||||
call plug#end()
|
||||
|
||||
set completeopt=menu,menuone,noselect
|
||||
|
||||
lua <<EOF
|
||||
-- Setup nvim-cmp.
|
||||
local cmp = require'cmp'
|
||||
|
||||
cmp.setup({
|
||||
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,
|
||||
},
|
||||
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-y>'] = cmp.config.disable, -- Specify `cmp.config.disable` if you want to remove the default `<C-y>` mapping.
|
||||
['<C-e>'] = cmp.mapping({
|
||||
i = cmp.mapping.abort(),
|
||||
c = cmp.mapping.close(),
|
||||
}),
|
||||
['<CR>'] = cmp.mapping.confirm({ select = true }),
|
||||
},
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'nvim_lsp' },
|
||||
{ name = 'vsnip' }, -- For vsnip users.
|
||||
-- { name = 'luasnip' }, -- For luasnip users.
|
||||
-- { name = 'ultisnips' }, -- For ultisnips users.
|
||||
-- { name = 'snippy' }, -- For snippy users.
|
||||
}, {
|
||||
{ name = 'buffer' },
|
||||
})
|
||||
})
|
||||
|
||||
-- Use buffer source for `/` (if you enabled `native_menu`, this won't work anymore).
|
||||
cmp.setup.cmdline('/', {
|
||||
sources = {
|
||||
{ name = 'buffer' }
|
||||
}
|
||||
})
|
||||
|
||||
-- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
|
||||
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())
|
||||
-- Replace <YOUR_LSP_SERVER> with each lsp server you've enabled.
|
||||
require('lspconfig')['<YOUR_LSP_SERVER>'].setup {
|
||||
capabilities = capabilities
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Where can I find more completion sources?
|
||||
|
||||
You can search for various completion sources [here](https://github.com/topics/nvim-cmp).
|
||||
|
||||
|
||||
Configuration options
|
||||
====================
|
||||
|
||||
You can specify the following configuration options via `cmp.setup { ... }`.
|
||||
|
||||
The configuration options will be merged with the [default config](./lua/cmp/config/default.lua).
|
||||
|
||||
If you want to remove a default option, set it to `false`.
|
||||
|
||||
|
||||
#### mapping (type: table<string, fun(fallback: function)>)
|
||||
|
||||
Defines the action of each key mapping. The following lists all the built-in actions:
|
||||
|
||||
- `cmp.mapping.select_prev_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
- `cmp.mapping.select_next_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
- `cmp.mapping.scroll_docs(number)`
|
||||
- `cmp.mapping.complete()`
|
||||
- `cmp.mapping.close()`
|
||||
- `cmp.mapping.abort()`
|
||||
- `cmp.mapping.confirm({ select = bool, behavior = cmp.ConfirmBehavior.{Insert,Replace} })`: If `select` is true and you haven't select any item, automatically selects the first item.
|
||||
|
||||
You can configure `nvim-cmp` to use these `cmp.mapping` like this:
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<C-n>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }),
|
||||
['<C-p>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }),
|
||||
['<Down>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<Up>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<C-d>'] = cmp.mapping.scroll_docs(-4),
|
||||
['<C-f>'] = cmp.mapping.scroll_docs(4),
|
||||
['<C-Space>'] = cmp.mapping.complete(),
|
||||
['<C-e>'] = cmp.mapping.close(),
|
||||
['<CR>'] = cmp.mapping.confirm({
|
||||
behavior = cmp.ConfirmBehavior.Replace,
|
||||
select = true,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
In addition, the mapping mode can be specified with the help of `cmp.mapping(...)`. The default is the insert mode (i) if not specified.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
...
|
||||
['<Tab>'] = cmp.mapping(cmp.mapping.select_next_item(), { 'i', 's' })
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The mapping mode can also be specified using a table. This is particularly useful to set different actions for each mode.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<CR>'] = cmp.mapping({
|
||||
i = cmp.mapping.confirm({ select = true }),
|
||||
c = cmp.mapping.confirm({ select = false }),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You can also provide a custom function as the action.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<Tab>'] = function(fallback)
|
||||
if ...some_condition... then
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('...', true, true, true), 'n', true)
|
||||
else
|
||||
fallback() -- The fallback function is treated as original mapped key. In this case, it might be `<Tab>`.
|
||||
end
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
#### enabled (type: fun(): boolean|boolean)
|
||||
|
||||
A boolean value, or a function returning a boolean, that specifies whether to enable nvim-cmp's features or not.
|
||||
|
||||
Default:
|
||||
|
||||
```lua
|
||||
function()
|
||||
return vim.api.nvim_buf_get_option(0, 'buftype') ~= 'prompt'
|
||||
end
|
||||
```
|
||||
|
||||
#### sources (type: table<cmp.SourceConfig>)
|
||||
|
||||
Lists all the global completion sources that will be enabled in all buffers.
|
||||
The order of the list defines the priority of each source. See the
|
||||
*sorting.priority_weight* option below.
|
||||
|
||||
It is possible to set up different sources for different filetypes using
|
||||
`FileType` autocommand and `cmp.setup.buffer` to override the global
|
||||
configuration.
|
||||
|
||||
```viml
|
||||
" Setup buffer configuration (nvim-lua source only enables in Lua filetype).
|
||||
autocmd FileType lua lua require'cmp'.setup.buffer {
|
||||
\ sources = {
|
||||
\ { name = 'nvim_lua' },
|
||||
\ { name = 'buffer' },
|
||||
\ },
|
||||
\ }
|
||||
```
|
||||
|
||||
Note that the source name isn't necessarily the source repository name. Source
|
||||
names are defined in the source repository README files. For example, look at
|
||||
the [hrsh7th/cmp-buffer](https://github.com/hrsh7th/cmp-buffer) source README
|
||||
which defines the source name as `buffer`.
|
||||
|
||||
#### sources[number].name (type: string)
|
||||
|
||||
The source name.
|
||||
|
||||
#### sources[number].opts (type: table)
|
||||
|
||||
The source customization options. It is defined by each source.
|
||||
|
||||
#### sources[number].priority (type: number|nil)
|
||||
|
||||
The priority of the source. If you don't specify it, the source priority will
|
||||
be determined by the default algorithm (see `sorting.priority_weight`).
|
||||
|
||||
#### sources[number].keyword_pattern (type: string)
|
||||
|
||||
The source specific keyword_pattern for override.
|
||||
|
||||
#### sources[number].keyword_length (type: number)
|
||||
|
||||
The source specific keyword_length for override.
|
||||
|
||||
#### sources[number].max_item_count (type: number)
|
||||
|
||||
The source specific maximum item count.
|
||||
|
||||
#### sources[number].group_index (type: number)
|
||||
|
||||
The source group index.
|
||||
|
||||
You can call built-in utility like `cmp.config.sources({ { name = 'a' } }, { { name = 'b' } })`.
|
||||
|
||||
#### preselect (type: cmp.PreselectMode)
|
||||
|
||||
Specify preselect mode. The following modes are available.
|
||||
|
||||
- `cmp.PreselectMode.Item`
|
||||
- If the item has `preselect = true`, `nvim-cmp` will preselect it.
|
||||
- `cmp.PreselectMode.None`
|
||||
- Disable preselect feature.
|
||||
|
||||
Default: `cmp.PreselectMode.Item`
|
||||
|
||||
#### completion.autocomplete (type: cmp.TriggerEvent[])
|
||||
|
||||
Which events should trigger `autocompletion`.
|
||||
|
||||
If you set this to `false`, `nvim-cmp` will not perform completion
|
||||
automatically. You can still use manual completion though (like omni-completion
|
||||
via the `cmp.mapping.complete` function).
|
||||
|
||||
Default: `{ types.cmp.TriggerEvent.TextChanged }`
|
||||
|
||||
#### completion.keyword_pattern (type: string)
|
||||
|
||||
The default keyword pattern. This value will be used if a source does not set
|
||||
a source specific pattern.
|
||||
|
||||
Default: `[[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]]`
|
||||
|
||||
#### completion.keyword_length (type: number)
|
||||
|
||||
The minimum length of a word to complete on; e.g., do not try to complete when the
|
||||
length of the word to the left of the cursor is less than `keyword_length`.
|
||||
|
||||
Default: `1`
|
||||
|
||||
#### completion.get_trigger_characters (type: fun(trigger_characters: string[]): string[])
|
||||
|
||||
The function to resolve trigger_characters.
|
||||
|
||||
Default: `function(trigger_characters) return trigger_characters end`
|
||||
|
||||
#### completion.completeopt (type: string)
|
||||
|
||||
vim's `completeopt` setting. Warning: Be careful when changing this value.
|
||||
|
||||
Default: `menu,menuone,noselect`
|
||||
|
||||
#### confirmation.default_behavior (type: cmp.ConfirmBehavior)
|
||||
|
||||
A default `cmp.ConfirmBehavior` value when to use confirmed by commitCharacters
|
||||
|
||||
Default: `cmp.ConfirmBehavior.Insert`
|
||||
|
||||
#### confirmation.get_commit_characters (type: fun(commit_characters: string[]): string[])
|
||||
|
||||
The function to resolve commit_characters.
|
||||
|
||||
#### sorting.priority_weight (type: number)
|
||||
|
||||
The score multiplier of source when calculating the items' priorities.
|
||||
Specifically, each item's original priority (given by its corresponding source)
|
||||
will be increased by `#sources - (source_index - 1)` multiplied by
|
||||
`priority_weight`. That is, the final priority is calculated by the following formula:
|
||||
|
||||
`final_score = orig_score + ((#sources - (source_index - 1)) * sorting.priority_weight)`
|
||||
|
||||
Default: `2`
|
||||
|
||||
#### sorting.comparators (type: function[])
|
||||
|
||||
When sorting completion items, the sort logic tries each function in
|
||||
`sorting.comparators` consecutively when comparing two items. The first function
|
||||
to return something other than `nil` takes precedence.
|
||||
|
||||
Each function must return `boolean|nil`.
|
||||
|
||||
You can use the preset functions from `cmp.config.compare.*`.
|
||||
|
||||
Default:
|
||||
```lua
|
||||
{
|
||||
cmp.config.compare.offset,
|
||||
cmp.config.compare.exact,
|
||||
cmp.config.compare.score,
|
||||
cmp.config.compare.recently_used,
|
||||
cmp.config.compare.kind,
|
||||
cmp.config.compare.sort_text,
|
||||
cmp.config.compare.length,
|
||||
cmp.config.compare.order,
|
||||
}
|
||||
```
|
||||
|
||||
#### documentation (type: false | cmp.DocumentationConfig)
|
||||
|
||||
If set to `false`, the documentation of each item will not be shown.
|
||||
Else, a table representing documentation configuration should be provided.
|
||||
The following are the possible options:
|
||||
|
||||
#### documentation.border (type: string[])
|
||||
|
||||
Border characters used for documentation window.
|
||||
|
||||
#### documentation.winhighlight (type: string)
|
||||
|
||||
A neovim's `winhighlight` option for documentation window.
|
||||
|
||||
#### documentation.maxwidth (type: number)
|
||||
|
||||
The documentation window's max width.
|
||||
|
||||
#### documentation.maxheight (type: number)
|
||||
|
||||
The documentation window's max height.
|
||||
|
||||
#### documentation.zindex (type: number)
|
||||
|
||||
The documentation window's zindex.
|
||||
|
||||
#### formatting.fields (type: cmp.ItemField[])
|
||||
|
||||
The order of item's fields for completion menu.
|
||||
|
||||
#### formatting.format (type: fun(entry: cmp.Entry, vim_item: vim.CompletedItem): vim.CompletedItem)
|
||||
|
||||
A function to customize completion menu.
|
||||
The return value is defined by vim. See `:help complete-items`.
|
||||
|
||||
You can display the fancy icons to completion-menu with [lspkind-nvim](https://github.com/onsails/lspkind-nvim).
|
||||
|
||||
Please see [FAQ](#how-to-show-name-of-item-kind-and-source-like-compe) if you would like to show symbol-text (e.g. function) and source (e.g. LSP) like compe.
|
||||
|
||||
```lua
|
||||
local lspkind = require('lspkind')
|
||||
cmp.setup {
|
||||
formatting = {
|
||||
format = lspkind.cmp_format(),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See the [wiki](https://github.com/hrsh7th/nvim-cmp/wiki/Menu-Appearance#basic-customisations) for more info on customizing menu appearance.
|
||||
|
||||
#### experimental.native_menu (type: boolean)
|
||||
|
||||
Use vim's native completion menu instead of custom floating menu.
|
||||
|
||||
Default: `false`
|
||||
|
||||
#### experimental.ghost_text (type: cmp.GhostTextConfig | false)
|
||||
|
||||
Specify whether to display ghost text.
|
||||
|
||||
Default: `false`
|
||||
|
||||
Commands
|
||||
====================
|
||||
|
||||
#### `CmpStatus`
|
||||
|
||||
Show the source statuses
|
||||
|
||||
Autocmds
|
||||
====================
|
||||
|
||||
#### `cmp#ready`
|
||||
|
||||
Invoke after nvim-cmp setup.
|
||||
|
||||
Highlights
|
||||
====================
|
||||
|
||||
#### `CmpItemAbbr`
|
||||
|
||||
The abbr field.
|
||||
|
||||
#### `CmpItemAbbrDeprecated`
|
||||
|
||||
The deprecated item's abbr field.
|
||||
|
||||
#### `CmpItemAbbrMatch`
|
||||
|
||||
The matched characters highlight.
|
||||
|
||||
#### `CmpItemAbbrMatchFuzzy`
|
||||
|
||||
The fuzzy matched characters highlight.
|
||||
|
||||
#### `CmpItemKind`
|
||||
|
||||
The kind field.
|
||||
|
||||
#### `CmpItemMenu`
|
||||
|
||||
The menu field.
|
||||
|
||||
Programatic API
|
||||
====================
|
||||
|
||||
You can use the following APIs.
|
||||
|
||||
#### `cmp.event:on(name: string, callback: string)`
|
||||
|
||||
Subscribes to the following events.
|
||||
|
||||
- `confirm_done`
|
||||
|
||||
#### `cmp.get_config()`
|
||||
|
||||
Returns the current configuration.
|
||||
|
||||
#### `cmp.visible()`
|
||||
|
||||
Returns the completion menu is visible or not.
|
||||
|
||||
NOTE: This method returns true if the native popup menu is visible, for the convenience of defining mappings.
|
||||
|
||||
#### `cmp.get_selected_entry()`
|
||||
|
||||
Returns the selected entry.
|
||||
|
||||
#### `cmp.get_active_entry()`
|
||||
|
||||
Returns the active entry.
|
||||
|
||||
NOTE: The `preselected` entry does not returned from this method.
|
||||
|
||||
#### `cmp.confirm({ select = boolean, behavior = cmp.ConfirmBehavior.{Insert,Replace} }, callback)`
|
||||
|
||||
Confirms the current selected item, if possible. If `select` is true and no item has been selected, selects the first item.
|
||||
|
||||
#### `cmp.complete()`
|
||||
|
||||
Invokes manual completion.
|
||||
|
||||
#### `cmp.close()`
|
||||
|
||||
Closes the current completion menu.
|
||||
|
||||
#### `cmp.abort()`
|
||||
|
||||
Closes the current completion menu and restore the current line (similar to native `<C-e>` behavior).
|
||||
|
||||
#### `cmp.select_next_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
|
||||
Selects the next completion item if possible.
|
||||
|
||||
#### `cmp.select_prev_item({ cmp.SelectBehavior.{Insert,Select} })`
|
||||
|
||||
Selects the previous completion item if possible.
|
||||
|
||||
#### `cmp.scroll_docs(delta)`
|
||||
|
||||
Scrolls the documentation window by `delta` lines, if possible.
|
||||
|
||||
|
||||
FAQ
|
||||
====================
|
||||
|
||||
#### I can't get the specific source working.
|
||||
|
||||
Check the output of command `:CmpStatus`. It is likely that you specify the source name incorrectly.
|
||||
|
||||
NOTE: `nvim_lsp` will be sourced on `InsertEnter` event. It will show as `unknown source`, but this isn't a problem.
|
||||
|
||||
|
||||
#### What is the `pair-wise plugin automatically supported`?
|
||||
|
||||
Some pair-wise plugin set up the mapping automatically.
|
||||
For example, `vim-endwise` will map `<CR>` even if you don't do any mapping instructions for the plugin.
|
||||
|
||||
But I think the user want to override `<CR>` mapping only when the mapping item is selected.
|
||||
|
||||
The `nvim-cmp` does it automatically.
|
||||
|
||||
The following configuration will be working as
|
||||
|
||||
1. If the completion-item is selected, will be working as `cmp.mapping.confirm`.
|
||||
2. If the completion-item isn't selected, will be working as vim-endwise feature.
|
||||
|
||||
```lua
|
||||
mapping = {
|
||||
['<CR>'] = cmp.mapping.confirm()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### What is the equivalence of nvim-compe's `preselect = 'always'`?
|
||||
|
||||
You can use the following configuration.
|
||||
|
||||
```lua
|
||||
cmp.setup {
|
||||
completion = {
|
||||
completeopt = 'menu,menuone,noinsert',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### I don't use a snippet plugin.
|
||||
|
||||
At the moment, nvim-cmp requires a snippet engine to function correctly.
|
||||
You need to specify one in `snippet`.
|
||||
|
||||
```lua
|
||||
snippet = {
|
||||
-- REQUIRED - you must specify a snippet engine
|
||||
expand = function(args)
|
||||
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
|
||||
-- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
|
||||
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
|
||||
-- require'snippy'.expand_snippet(args.body) -- For `snippy` users.
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### I dislike auto-completion
|
||||
|
||||
You can use `nvim-cmp` without auto-completion like this.
|
||||
|
||||
```lua
|
||||
cmp.setup {
|
||||
completion = {
|
||||
autocomplete = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### How to disable nvim-cmp on the specific buffer?
|
||||
|
||||
You can specify `enabled = false` like this.
|
||||
|
||||
```vim
|
||||
autocmd FileType TelescopePrompt lua require('cmp').setup.buffer { enabled = false }
|
||||
```
|
||||
|
||||
|
||||
#### nvim-cmp is slow.
|
||||
|
||||
I've optimized `nvim-cmp` as much as possible, but there are currently some known / unfixable issues.
|
||||
|
||||
**`cmp-buffer` source and too large buffer**
|
||||
|
||||
The `cmp-buffer` source makes an index of the current buffer so if the current buffer is too large, it will slowdown the main UI thread.
|
||||
|
||||
**`vim.lsp.set_log_level`**
|
||||
|
||||
This setting will cause the filesystem operation for each LSP payload.
|
||||
This will greatly slow down nvim-cmp (and other LSP related features).
|
||||
|
||||
|
||||
#### How to show name of item kind and source (like compe)?
|
||||
|
||||
```lua
|
||||
formatting = {
|
||||
format = require("lspkind").cmp_format({with_text = true, menu = ({
|
||||
buffer = "[Buffer]",
|
||||
nvim_lsp = "[LSP]",
|
||||
luasnip = "[LuaSnip]",
|
||||
nvim_lua = "[Lua]",
|
||||
latex_symbols = "[Latex]",
|
||||
})}),
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
#### How to set up mappings?
|
||||
|
||||
You can find all the mapping examples in [Example mappings](https://github.com/hrsh7th/nvim-cmp/wiki/Example-mappings).
|
||||
|
||||
|
||||
Create a Custom Source
|
||||
====================
|
||||
|
||||
Warning: If the LSP spec is changed, nvim-cmp will keep up to it without an announcement.
|
||||
|
||||
If you publish `nvim-cmp` source to GitHub, please add `nvim-cmp` topic for the repo.
|
||||
|
||||
You should read [cmp types](/lua/cmp/types) and [LSP spec](https://microsoft.github.io/language-server-protocol/specifications/specification-current/) to create sources.
|
||||
|
||||
- The `complete` function is required. Others can be omitted.
|
||||
- The `callback` argument must always be called.
|
||||
- The custom source should only use `require('cmp')`.
|
||||
- The custom source can specify `word` property to CompletionItem. (It isn't an LSP specification but supported as a special case.)
|
||||
|
||||
Here is an example of a custom source.
|
||||
|
||||
```lua
|
||||
local source = {}
|
||||
|
||||
---Source constructor.
|
||||
source.new = function()
|
||||
local self = setmetatable({}, { __index = source })
|
||||
self.your_awesome_variable = 1
|
||||
return self
|
||||
end
|
||||
|
||||
---Return the source is available or not.
|
||||
---@return boolean
|
||||
function source:is_available()
|
||||
return true
|
||||
end
|
||||
|
||||
---Return the source name for some information.
|
||||
function source:get_debug_name()
|
||||
return 'example'
|
||||
end
|
||||
|
||||
---Return keyword pattern which will be used...
|
||||
--- 1. Trigger keyword completion
|
||||
--- 2. Detect menu start offset
|
||||
--- 3. Reset completion state
|
||||
---@param params cmp.SourceBaseApiParams
|
||||
---@return string
|
||||
function source:get_keyword_pattern(params)
|
||||
return '???'
|
||||
end
|
||||
|
||||
---Return trigger characters.
|
||||
---@param params cmp.SourceBaseApiParams
|
||||
---@return string[]
|
||||
function source:get_trigger_characters(params)
|
||||
return { ??? }
|
||||
end
|
||||
|
||||
---Invoke completion (required).
|
||||
--- If you want to abort completion, just call the callback without arguments.
|
||||
---@param params cmp.SourceCompletionApiParams
|
||||
---@param callback fun(response: lsp.CompletionResponse|nil)
|
||||
function source:complete(params, callback)
|
||||
callback({
|
||||
{ label = 'January' },
|
||||
{ label = 'February' },
|
||||
{ label = 'March' },
|
||||
{ label = 'April' },
|
||||
{ label = 'May' },
|
||||
{ label = 'June' },
|
||||
{ label = 'July' },
|
||||
{ label = 'August' },
|
||||
{ label = 'September' },
|
||||
{ label = 'October' },
|
||||
{ label = 'November' },
|
||||
{ label = 'December' },
|
||||
})
|
||||
end
|
||||
|
||||
---Resolve completion item that will be called when the item selected or before the item confirmation.
|
||||
---@param completion_item lsp.CompletionItem
|
||||
---@param callback fun(completion_item: lsp.CompletionItem|nil)
|
||||
function source:resolve(completion_item, callback)
|
||||
callback(completion_item)
|
||||
end
|
||||
|
||||
---Execute command that will be called when after the item confirmation.
|
||||
---@param completion_item lsp.CompletionItem
|
||||
---@param callback fun(completion_item: lsp.CompletionItem|nil)
|
||||
function source:execute(completion_item, callback)
|
||||
callback(completion_item)
|
||||
end
|
||||
|
||||
require('cmp').register_source(source.new())
|
||||
```
|
||||
|
||||
You can also create a source by Vim script like this (This is useful to support callback style plugins).
|
||||
|
||||
- If you want to return `boolean`, you must return `v:true`/`v:false` instead of `0`/`1`.
|
||||
|
||||
```vim
|
||||
let s:source = {}
|
||||
|
||||
function! s:source.new() abort
|
||||
return extend(deepcopy(s:source))
|
||||
endfunction
|
||||
|
||||
" The other APIs are also available.
|
||||
|
||||
function! s:source.complete(params, callback) abort
|
||||
call a:callback({
|
||||
\ { 'label': 'January' },
|
||||
\ { 'label': 'February' },
|
||||
\ { 'label': 'March' },
|
||||
\ { 'label': 'April' },
|
||||
\ { 'label': 'May' },
|
||||
\ { 'label': 'June' },
|
||||
\ { 'label': 'July' },
|
||||
\ { 'label': 'August' },
|
||||
\ { 'label': 'September' },
|
||||
\ { 'label': 'October' },
|
||||
\ { 'label': 'November' },
|
||||
\ { 'label': 'December' },
|
||||
\ })
|
||||
endfunction
|
||||
|
||||
call cmp#register_source('month', s:source.new())
|
||||
```
|
76
bundle/nvim-cmp/autoload/cmp.vim
Normal file
76
bundle/nvim-cmp/autoload/cmp.vim
Normal file
@ -0,0 +1,76 @@
|
||||
let s:bridge_id = 0
|
||||
let s:sources = {}
|
||||
|
||||
"
|
||||
" cmp#apply_text_edits
|
||||
"
|
||||
" TODO: Remove this if nvim's apply_text_edits will be improved.
|
||||
"
|
||||
function! cmp#apply_text_edits(bufnr, text_edits) abort
|
||||
if !exists('s:TextEdit')
|
||||
let s:TextEdit = vital#cmp#import('VS.LSP.TextEdit')
|
||||
endif
|
||||
call s:TextEdit.apply(a:bufnr, a:text_edits)
|
||||
endfunction
|
||||
|
||||
"
|
||||
" cmp#register_source
|
||||
"
|
||||
function! cmp#register_source(name, source) abort
|
||||
let l:methods = []
|
||||
for l:method in ['is_available', 'get_debug_name', 'get_trigger_characters', 'get_keyword_pattern', 'complete', 'execute', 'resolve']
|
||||
if has_key(a:source, l:method) && type(a:source[l:method]) == v:t_func
|
||||
call add(l:methods, l:method)
|
||||
endif
|
||||
endfor
|
||||
|
||||
let s:bridge_id += 1
|
||||
let a:source.bridge_id = s:bridge_id
|
||||
let a:source.id = luaeval('require("cmp").register_source(_A[1], require("cmp.vim_source").new(_A[2], _A[3]))', [a:name, s:bridge_id, l:methods])
|
||||
let s:sources[s:bridge_id] = a:source
|
||||
return a:source.id
|
||||
endfunction
|
||||
|
||||
"
|
||||
" cmp#unregister_source
|
||||
"
|
||||
function! cmp#unregister_source(id) abort
|
||||
if has_key(s:sources, a:id)
|
||||
unlet s:sources[a:id]
|
||||
endif
|
||||
call luaeval('require("cmp").unregister_source(_A)', a:id)
|
||||
endfunction
|
||||
|
||||
"
|
||||
" cmp#_method
|
||||
"
|
||||
function! cmp#_method(bridge_id, method, args) abort
|
||||
try
|
||||
let l:source = s:sources[a:bridge_id]
|
||||
if a:method ==# 'is_available'
|
||||
return l:source[a:method]()
|
||||
elseif a:method ==# 'get_debug_name'
|
||||
return l:source[a:method]()
|
||||
elseif a:method ==# 'get_keyword_pattern'
|
||||
return l:source[a:method](a:args[0])
|
||||
elseif a:method ==# 'get_trigger_characters'
|
||||
return l:source[a:method](a:args[0])
|
||||
elseif a:method ==# 'complete'
|
||||
return l:source[a:method](a:args[0], s:callback(a:args[1]))
|
||||
elseif a:method ==# 'resolve'
|
||||
return l:source[a:method](a:args[0], s:callback(a:args[1]))
|
||||
elseif a:method ==# 'execute'
|
||||
return l:source[a:method](a:args[0], s:callback(a:args[1]))
|
||||
endif
|
||||
catch /.*/
|
||||
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
|
||||
endtry
|
||||
return v:null
|
||||
endfunction
|
||||
|
||||
"
|
||||
" s:callback
|
||||
"
|
||||
function! s:callback(id) abort
|
||||
return { ... -> luaeval('require("cmp.vim_source").on_callback(_A[1], _A[2])', [a:id, a:000]) }
|
||||
endfunction
|
9
bundle/nvim-cmp/autoload/vital/_cmp.vim
Normal file
9
bundle/nvim-cmp/autoload/vital/_cmp.vim
Normal file
@ -0,0 +1,9 @@
|
||||
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
|
62
bundle/nvim-cmp/autoload/vital/_cmp/VS/LSP/Position.vim
Normal file
62
bundle/nvim-cmp/autoload/vital/_cmp/VS/LSP/Position.vim
Normal file
@ -0,0 +1,62 @@
|
||||
" ___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
|
||||
|
23
bundle/nvim-cmp/autoload/vital/_cmp/VS/LSP/Text.vim
Normal file
23
bundle/nvim-cmp/autoload/vital/_cmp/VS/LSP/Text.vim
Normal file
@ -0,0 +1,23 @@
|
||||
" ___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
|
||||
|
185
bundle/nvim-cmp/autoload/vital/_cmp/VS/LSP/TextEdit.vim
Normal file
185
bundle/nvim-cmp/autoload/vital/_cmp/VS/LSP/TextEdit.vim
Normal file
@ -0,0 +1,185 @@
|
||||
" ___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
|
||||
|
126
bundle/nvim-cmp/autoload/vital/_cmp/VS/Vim/Buffer.vim
Normal file
126
bundle/nvim-cmp/autoload/vital/_cmp/VS/Vim/Buffer.vim
Normal file
@ -0,0 +1,126 @@
|
||||
" ___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
|
||||
|
21
bundle/nvim-cmp/autoload/vital/_cmp/VS/Vim/Option.vim
Normal file
21
bundle/nvim-cmp/autoload/vital/_cmp/VS/Vim/Option.vim
Normal file
@ -0,0 +1,21 @@
|
||||
" ___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
|
||||
|
330
bundle/nvim-cmp/autoload/vital/cmp.vim
Normal file
330
bundle/nvim-cmp/autoload/vital/cmp.vim
Normal file
@ -0,0 +1,330 @@
|
||||
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
|
4
bundle/nvim-cmp/autoload/vital/cmp.vital
Normal file
4
bundle/nvim-cmp/autoload/vital/cmp.vital
Normal file
@ -0,0 +1,4 @@
|
||||
cmp
|
||||
2755f0c8fbd3442bcb7f567832e4d1455b57f9a2
|
||||
|
||||
VS.LSP.TextEdit
|
7
bundle/nvim-cmp/init.sh
Normal file
7
bundle/nvim-cmp/init.sh
Normal file
@ -0,0 +1,7 @@
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
rm $DIR/.git/hooks/*
|
||||
cp $DIR/.githooks/* $DIR/.git/hooks/
|
||||
chmod 755 $DIR/.git/hooks/*
|
||||
|
||||
|
104
bundle/nvim-cmp/lua/cmp/config.lua
Normal file
104
bundle/nvim-cmp/lua/cmp/config.lua
Normal file
@ -0,0 +1,104 @@
|
||||
local mapping = require('cmp.config.mapping')
|
||||
local cache = require('cmp.utils.cache')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
---@class cmp.Config
|
||||
---@field public g cmp.ConfigSchema
|
||||
local config = {}
|
||||
|
||||
---@type cmp.Cache
|
||||
config.cache = cache.new()
|
||||
|
||||
---@type cmp.ConfigSchema
|
||||
config.global = require('cmp.config.default')()
|
||||
|
||||
---@type table<number, cmp.ConfigSchema>
|
||||
config.buffers = {}
|
||||
|
||||
---@type table<string, cmp.ConfigSchema>
|
||||
config.cmdline = {}
|
||||
|
||||
---Set configuration for global.
|
||||
---@param c cmp.ConfigSchema
|
||||
config.set_global = function(c)
|
||||
config.global = misc.merge(c, config.global)
|
||||
config.global.revision = config.global.revision or 1
|
||||
config.global.revision = config.global.revision + 1
|
||||
end
|
||||
|
||||
---Set configuration for buffer
|
||||
---@param c cmp.ConfigSchema
|
||||
---@param bufnr number|nil
|
||||
config.set_buffer = function(c, bufnr)
|
||||
local revision = (config.buffers[bufnr] or {}).revision or 1
|
||||
config.buffers[bufnr] = c
|
||||
config.buffers[bufnr].revision = revision + 1
|
||||
end
|
||||
|
||||
---Set configuration for cmdline
|
||||
config.set_cmdline = function(c, type)
|
||||
local revision = (config.cmdline[type] or {}).revision or 1
|
||||
config.cmdline[type] = c
|
||||
config.cmdline[type].revision = revision + 1
|
||||
end
|
||||
|
||||
---@return cmp.ConfigSchema
|
||||
config.get = function()
|
||||
local global = config.global
|
||||
if api.is_cmdline_mode() then
|
||||
local type = vim.fn.getcmdtype()
|
||||
local cmdline = config.cmdline[type] or { revision = 1, sources = {} }
|
||||
return config.cache:ensure({ 'get_cmdline', type, global.revision or 0, cmdline.revision or 0 }, function()
|
||||
return misc.merge(config.normalize(cmdline), config.normalize(global))
|
||||
end)
|
||||
else
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local buffer = config.buffers[bufnr] or { revision = 1 }
|
||||
return config.cache:ensure({ 'get_buffer', bufnr, global.revision or 0, buffer.revision or 0 }, function()
|
||||
return misc.merge(config.normalize(buffer), config.normalize(global))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---Return cmp is enabled or not.
|
||||
config.enabled = function()
|
||||
local enabled = config.get().enabled
|
||||
if type(enabled) == 'function' then
|
||||
enabled = enabled()
|
||||
end
|
||||
return enabled and api.is_suitable_mode()
|
||||
end
|
||||
|
||||
---Return source config
|
||||
---@param name string
|
||||
---@return cmp.SourceConfig
|
||||
config.get_source_config = function(name)
|
||||
local c = config.get()
|
||||
for _, s in ipairs(c.sources) do
|
||||
if s.name == name then
|
||||
if type(s.opts) ~= 'table' then
|
||||
s.opts = {}
|
||||
end
|
||||
return s
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Normalize mapping key
|
||||
---@param c cmp.ConfigSchema
|
||||
---@return cmp.ConfigSchema
|
||||
config.normalize = function(c)
|
||||
if c.mapping then
|
||||
local normalized = {}
|
||||
for k, v in pairs(c.mapping) do
|
||||
normalized[keymap.normalize(k)] = mapping(v, { 'i' })
|
||||
end
|
||||
c.mapping = normalized
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
return config
|
103
bundle/nvim-cmp/lua/cmp/config/compare.lua
Normal file
103
bundle/nvim-cmp/lua/cmp/config/compare.lua
Normal file
@ -0,0 +1,103 @@
|
||||
local types = require('cmp.types')
|
||||
local misc = require('cmp.utils.misc')
|
||||
|
||||
local compare = {}
|
||||
|
||||
-- offset
|
||||
compare.offset = function(entry1, entry2)
|
||||
local diff = entry1:get_offset() - entry2:get_offset()
|
||||
if diff < 0 then
|
||||
return true
|
||||
elseif diff > 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- exact
|
||||
compare.exact = function(entry1, entry2)
|
||||
if entry1.exact ~= entry2.exact then
|
||||
return entry1.exact
|
||||
end
|
||||
end
|
||||
|
||||
-- score
|
||||
compare.score = function(entry1, entry2)
|
||||
local diff = entry2.score - entry1.score
|
||||
if diff < 0 then
|
||||
return true
|
||||
elseif diff > 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- recently_used
|
||||
compare.recently_used = setmetatable({
|
||||
records = {},
|
||||
add_entry = function(self, e)
|
||||
self.records[e.completion_item.label] = vim.loop.now()
|
||||
end,
|
||||
}, {
|
||||
__call = function(self, entry1, entry2)
|
||||
local t1 = self.records[entry1.completion_item.label] or -1
|
||||
local t2 = self.records[entry2.completion_item.label] or -1
|
||||
if t1 ~= t2 then
|
||||
return t1 > t2
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- kind
|
||||
compare.kind = function(entry1, entry2)
|
||||
local kind1 = entry1:get_kind()
|
||||
kind1 = kind1 == types.lsp.CompletionItemKind.Text and 100 or kind1
|
||||
local kind2 = entry2:get_kind()
|
||||
kind2 = kind2 == types.lsp.CompletionItemKind.Text and 100 or kind2
|
||||
if kind1 ~= kind2 then
|
||||
if kind1 == types.lsp.CompletionItemKind.Snippet then
|
||||
return true
|
||||
end
|
||||
if kind2 == types.lsp.CompletionItemKind.Snippet then
|
||||
return false
|
||||
end
|
||||
local diff = kind1 - kind2
|
||||
if diff < 0 then
|
||||
return true
|
||||
elseif diff > 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- sortText
|
||||
compare.sort_text = function(entry1, entry2)
|
||||
if misc.safe(entry1.completion_item.sortText) and misc.safe(entry2.completion_item.sortText) then
|
||||
local diff = vim.stricmp(entry1.completion_item.sortText, entry2.completion_item.sortText)
|
||||
if diff < 0 then
|
||||
return true
|
||||
elseif diff > 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- length
|
||||
compare.length = function(entry1, entry2)
|
||||
local diff = #entry1.completion_item.label - #entry2.completion_item.label
|
||||
if diff < 0 then
|
||||
return true
|
||||
elseif diff > 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- order
|
||||
compare.order = function(entry1, entry2)
|
||||
local diff = entry1.id - entry2.id
|
||||
if diff < 0 then
|
||||
return true
|
||||
elseif diff > 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return compare
|
130
bundle/nvim-cmp/lua/cmp/config/default.lua
Normal file
130
bundle/nvim-cmp/lua/cmp/config/default.lua
Normal file
@ -0,0 +1,130 @@
|
||||
local compare = require('cmp.config.compare')
|
||||
local mapping = require('cmp.config.mapping')
|
||||
local types = require('cmp.types')
|
||||
|
||||
local WIDE_HEIGHT = 40
|
||||
|
||||
---@return cmp.ConfigSchema
|
||||
return function()
|
||||
return {
|
||||
enabled = function()
|
||||
return vim.api.nvim_buf_get_option(0, 'buftype') ~= 'prompt'
|
||||
end,
|
||||
completion = {
|
||||
autocomplete = {
|
||||
types.cmp.TriggerEvent.TextChanged,
|
||||
},
|
||||
completeopt = 'menu,menuone,noselect',
|
||||
keyword_pattern = [[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]],
|
||||
keyword_length = 1,
|
||||
get_trigger_characters = function(trigger_characters)
|
||||
return trigger_characters
|
||||
end,
|
||||
},
|
||||
|
||||
snippet = {
|
||||
expand = function()
|
||||
error('snippet engine is not configured.')
|
||||
end,
|
||||
},
|
||||
|
||||
preselect = types.cmp.PreselectMode.Item,
|
||||
|
||||
documentation = {
|
||||
border = { '', '', '', ' ', '', '', '', ' ' },
|
||||
winhighlight = 'NormalFloat:NormalFloat,FloatBorder:NormalFloat',
|
||||
maxwidth = math.floor((WIDE_HEIGHT * 2) * (vim.o.columns / (WIDE_HEIGHT * 2 * 16 / 9))),
|
||||
maxheight = math.floor(WIDE_HEIGHT * (WIDE_HEIGHT / vim.o.lines)),
|
||||
},
|
||||
|
||||
confirmation = {
|
||||
default_behavior = types.cmp.ConfirmBehavior.Insert,
|
||||
get_commit_characters = function(commit_characters)
|
||||
return commit_characters
|
||||
end,
|
||||
},
|
||||
|
||||
sorting = {
|
||||
priority_weight = 2,
|
||||
comparators = {
|
||||
compare.offset,
|
||||
compare.exact,
|
||||
compare.score,
|
||||
compare.recently_used,
|
||||
compare.kind,
|
||||
compare.sort_text,
|
||||
compare.length,
|
||||
compare.order,
|
||||
},
|
||||
},
|
||||
|
||||
event = {},
|
||||
|
||||
mapping = {
|
||||
['<Down>'] = mapping({
|
||||
i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }),
|
||||
c = function(fallback)
|
||||
local cmp = require('cmp')
|
||||
cmp.close()
|
||||
vim.schedule(cmp.suspend())
|
||||
fallback()
|
||||
end,
|
||||
}),
|
||||
['<Up>'] = mapping({
|
||||
i = mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Select }),
|
||||
c = function(fallback)
|
||||
local cmp = require('cmp')
|
||||
cmp.close()
|
||||
vim.schedule(cmp.suspend())
|
||||
fallback()
|
||||
end,
|
||||
}),
|
||||
['<Tab>'] = mapping({
|
||||
c = function(fallback)
|
||||
local cmp = require('cmp')
|
||||
if #cmp.core:get_sources() > 0 and not cmp.get_config().experimental.native_menu then
|
||||
if cmp.visible() then
|
||||
cmp.select_next_item()
|
||||
else
|
||||
cmp.complete()
|
||||
end
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end,
|
||||
}),
|
||||
['<S-Tab>'] = mapping({
|
||||
c = function(fallback)
|
||||
local cmp = require('cmp')
|
||||
if #cmp.core:get_sources() > 0 and not cmp.get_config().experimental.native_menu then
|
||||
if cmp.visible() then
|
||||
cmp.select_prev_item()
|
||||
else
|
||||
cmp.complete()
|
||||
end
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end,
|
||||
}),
|
||||
['<C-n>'] = mapping(mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i', 'c' }),
|
||||
['<C-p>'] = mapping(mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i', 'c' }),
|
||||
['<C-y>'] = mapping.confirm({ select = false }),
|
||||
['<C-e>'] = mapping.abort(),
|
||||
},
|
||||
|
||||
formatting = {
|
||||
fields = { 'abbr', 'kind', 'menu' },
|
||||
format = function(_, vim_item)
|
||||
return vim_item
|
||||
end,
|
||||
},
|
||||
|
||||
experimental = {
|
||||
native_menu = false,
|
||||
ghost_text = false,
|
||||
},
|
||||
|
||||
sources = {},
|
||||
}
|
||||
end
|
82
bundle/nvim-cmp/lua/cmp/config/mapping.lua
Normal file
82
bundle/nvim-cmp/lua/cmp/config/mapping.lua
Normal file
@ -0,0 +1,82 @@
|
||||
local mapping
|
||||
mapping = setmetatable({}, {
|
||||
__call = function(_, invoke, modes)
|
||||
if type(invoke) == 'function' then
|
||||
local map = {}
|
||||
for _, mode in ipairs(modes or { 'i' }) do
|
||||
map[mode] = invoke
|
||||
end
|
||||
return map
|
||||
end
|
||||
return invoke
|
||||
end,
|
||||
})
|
||||
|
||||
---Invoke completion
|
||||
mapping.complete = function()
|
||||
return function(fallback)
|
||||
if not require('cmp').complete() then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Close current completion menu if it displayed.
|
||||
mapping.close = function()
|
||||
return function(fallback)
|
||||
if not require('cmp').close() then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Abort current completion menu if it displayed.
|
||||
mapping.abort = function()
|
||||
return function(fallback)
|
||||
if not require('cmp').abort() then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Scroll documentation window.
|
||||
mapping.scroll_docs = function(delta)
|
||||
return function(fallback)
|
||||
if not require('cmp').scroll_docs(delta) then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Select next completion item.
|
||||
mapping.select_next_item = function(option)
|
||||
return function(fallback)
|
||||
if not require('cmp').select_next_item(option) then
|
||||
local release = require('cmp').core:suspend()
|
||||
fallback()
|
||||
vim.schedule(release)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Select prev completion item.
|
||||
mapping.select_prev_item = function(option)
|
||||
return function(fallback)
|
||||
if not require('cmp').select_prev_item(option) then
|
||||
local release = require('cmp').core:suspend()
|
||||
fallback()
|
||||
vim.schedule(release)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Confirm selection
|
||||
mapping.confirm = function(option)
|
||||
return function(fallback)
|
||||
if not require('cmp').confirm(option) then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return mapping
|
10
bundle/nvim-cmp/lua/cmp/config/sources.lua
Normal file
10
bundle/nvim-cmp/lua/cmp/config/sources.lua
Normal file
@ -0,0 +1,10 @@
|
||||
return function(...)
|
||||
local sources = {}
|
||||
for i, group in ipairs({ ... }) do
|
||||
for _, source in ipairs(group) do
|
||||
source.group_index = i
|
||||
table.insert(sources, source)
|
||||
end
|
||||
end
|
||||
return sources
|
||||
end
|
105
bundle/nvim-cmp/lua/cmp/context.lua
Normal file
105
bundle/nvim-cmp/lua/cmp/context.lua
Normal file
@ -0,0 +1,105 @@
|
||||
local misc = require('cmp.utils.misc')
|
||||
local pattern = require('cmp.utils.pattern')
|
||||
local types = require('cmp.types')
|
||||
local cache = require('cmp.utils.cache')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
---@class cmp.Context
|
||||
---@field public id string
|
||||
---@field public cache cmp.Cache
|
||||
---@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 cursor vim.Position|lsp.Position
|
||||
---@field public cursor_line string
|
||||
---@field public cursor_after_line string
|
||||
---@field public cursor_before_line string
|
||||
local context = {}
|
||||
|
||||
---Create new empty context
|
||||
---@return cmp.Context
|
||||
context.empty = function()
|
||||
local ctx = context.new({}) -- dirty hack to prevent recursive call `context.empty`.
|
||||
ctx.bufnr = -1
|
||||
ctx.input = ''
|
||||
ctx.cursor = {}
|
||||
ctx.cursor.row = -1
|
||||
ctx.cursor.col = -1
|
||||
return ctx
|
||||
end
|
||||
|
||||
---Create new context
|
||||
---@param prev_context cmp.Context
|
||||
---@param option cmp.ContextOption
|
||||
---@return cmp.Context
|
||||
context.new = function(prev_context, option)
|
||||
option = option or {}
|
||||
|
||||
local self = setmetatable({}, { __index = context })
|
||||
self.id = misc.id('cmp.context.new')
|
||||
self.cache = cache.new()
|
||||
self.prev_context = prev_context or context.empty()
|
||||
self.option = option or { reason = types.cmp.ContextReason.None }
|
||||
self.filetype = vim.api.nvim_buf_get_option(0, 'filetype')
|
||||
self.time = vim.loop.now()
|
||||
self.bufnr = vim.api.nvim_get_current_buf()
|
||||
|
||||
local cursor = api.get_cursor()
|
||||
self.cursor_line = api.get_current_line()
|
||||
self.cursor = {}
|
||||
self.cursor.row = cursor[1]
|
||||
self.cursor.col = cursor[2] + 1
|
||||
self.cursor.line = self.cursor.row - 1
|
||||
self.cursor.character = misc.to_utfindex(self.cursor_line, self.cursor.col)
|
||||
self.cursor_before_line = string.sub(self.cursor_line, 1, self.cursor.col - 1)
|
||||
self.cursor_after_line = string.sub(self.cursor_line, self.cursor.col)
|
||||
return self
|
||||
end
|
||||
|
||||
---Return context creation reason.
|
||||
---@return cmp.ContextReason
|
||||
context.get_reason = function(self)
|
||||
return self.option.reason
|
||||
end
|
||||
|
||||
---Get keyword pattern offset
|
||||
---@return number|nil
|
||||
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
|
||||
end)
|
||||
end
|
||||
|
||||
---Return if this context is changed from previous context or not.
|
||||
---@return boolean
|
||||
context.changed = function(self, ctx)
|
||||
local curr = self
|
||||
|
||||
if curr.bufnr ~= ctx.bufnr then
|
||||
return true
|
||||
end
|
||||
if curr.cursor.row ~= ctx.cursor.row then
|
||||
return true
|
||||
end
|
||||
if curr.cursor.col ~= ctx.cursor.col then
|
||||
return true
|
||||
end
|
||||
if curr:get_reason() == types.cmp.ContextReason.Manual then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---Shallow clone
|
||||
context.clone = function(self)
|
||||
local cloned = {}
|
||||
for k, v in pairs(self) do
|
||||
cloned[k] = v
|
||||
end
|
||||
return cloned
|
||||
end
|
||||
|
||||
return context
|
31
bundle/nvim-cmp/lua/cmp/context_spec.lua
Normal file
31
bundle/nvim-cmp/lua/cmp/context_spec.lua
Normal file
@ -0,0 +1,31 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
|
||||
local context = require('cmp.context')
|
||||
|
||||
describe('context', function()
|
||||
before_each(spec.before)
|
||||
|
||||
describe('new', function()
|
||||
it('middle of text', function()
|
||||
vim.fn.setline('1', 'function! s:name() abort')
|
||||
vim.bo.filetype = 'vim'
|
||||
vim.fn.execute('normal! fm')
|
||||
local ctx = context.new()
|
||||
assert.are.equal(ctx.filetype, 'vim')
|
||||
assert.are.equal(ctx.cursor.row, 1)
|
||||
assert.are.equal(ctx.cursor.col, 15)
|
||||
assert.are.equal(ctx.cursor_line, 'function! s:name() abort')
|
||||
end)
|
||||
|
||||
it('tab indent', function()
|
||||
vim.fn.setline('1', '\t\tab')
|
||||
vim.bo.filetype = 'vim'
|
||||
vim.fn.execute('normal! fb')
|
||||
local ctx = context.new()
|
||||
assert.are.equal(ctx.filetype, 'vim')
|
||||
assert.are.equal(ctx.cursor.row, 1)
|
||||
assert.are.equal(ctx.cursor.col, 4)
|
||||
assert.are.equal(ctx.cursor_line, '\t\tab')
|
||||
end)
|
||||
end)
|
||||
end)
|
435
bundle/nvim-cmp/lua/cmp/core.lua
Normal file
435
bundle/nvim-cmp/lua/cmp/core.lua
Normal file
@ -0,0 +1,435 @@
|
||||
local debug = require('cmp.utils.debug')
|
||||
local char = require('cmp.utils.char')
|
||||
local pattern = require('cmp.utils.pattern')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local async = require('cmp.utils.async')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local context = require('cmp.context')
|
||||
local source = require('cmp.source')
|
||||
local view = require('cmp.view')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local config = require('cmp.config')
|
||||
local types = require('cmp.types')
|
||||
local api = require('cmp.utils.api')
|
||||
local event = require('cmp.utils.event')
|
||||
|
||||
local SOURCE_TIMEOUT = 500
|
||||
local THROTTLE_TIME = 120
|
||||
local DEBOUNCE_TIME = 20
|
||||
|
||||
---@class cmp.Core
|
||||
---@field public suspending boolean
|
||||
---@field public view cmp.View
|
||||
---@field public sources cmp.Source[]
|
||||
---@field public sources_by_name table<string, cmp.Source>
|
||||
---@field public context cmp.Context
|
||||
---@field public event cmp.Event
|
||||
local core = {}
|
||||
|
||||
core.new = function()
|
||||
local self = setmetatable({}, { __index = core })
|
||||
self.suspending = false
|
||||
self.sources = {}
|
||||
self.sources_by_name = {}
|
||||
self.context = context.new()
|
||||
self.event = event.new()
|
||||
self.view = view.new()
|
||||
self.view.event:on('keymap', function(...)
|
||||
self:on_keymap(...)
|
||||
end)
|
||||
return self
|
||||
end
|
||||
|
||||
---Register source
|
||||
---@param s cmp.Source
|
||||
core.register_source = function(self, s)
|
||||
self.sources[s.id] = s
|
||||
if not self.sources_by_name[s.name] then
|
||||
self.sources_by_name[s.name] = {}
|
||||
end
|
||||
table.insert(self.sources_by_name[s.name], s)
|
||||
end
|
||||
|
||||
---Unregister source
|
||||
---@param source_id string
|
||||
core.unregister_source = function(self, source_id)
|
||||
local name = self.sources[source_id].name
|
||||
self.sources_by_name[name] = vim.tbl_filter(function(s)
|
||||
return s.id ~= source_id
|
||||
end, self.sources_by_name[name])
|
||||
self.sources[source_id] = nil
|
||||
end
|
||||
|
||||
---Get new context
|
||||
---@param option cmp.ContextOption
|
||||
---@return cmp.Context
|
||||
core.get_context = function(self, option)
|
||||
local prev = self.context:clone()
|
||||
prev.prev_context = nil
|
||||
local ctx = context.new(prev, option)
|
||||
self:set_context(ctx)
|
||||
return self.context
|
||||
end
|
||||
|
||||
---Set new context
|
||||
---@param ctx cmp.Context
|
||||
core.set_context = function(self, ctx)
|
||||
self.context = ctx
|
||||
end
|
||||
|
||||
---Suspend completion
|
||||
core.suspend = function(self)
|
||||
self.suspending = true
|
||||
return function()
|
||||
self.suspending = false
|
||||
end
|
||||
end
|
||||
|
||||
---Get sources that sorted by priority
|
||||
---@param statuses cmp.SourceStatus[]
|
||||
---@return cmp.Source[]
|
||||
core.get_sources = function(self, statuses)
|
||||
local sources = {}
|
||||
for _, c in pairs(config.get().sources) do
|
||||
for _, s in ipairs(self.sources_by_name[c.name] or {}) do
|
||||
if not statuses or vim.tbl_contains(statuses, s.status) then
|
||||
if s:is_available() then
|
||||
table.insert(sources, s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return sources
|
||||
end
|
||||
|
||||
---Keypress handler
|
||||
core.on_keymap = function(self, keys, fallback)
|
||||
local mode = api.get_mode()
|
||||
for key, mapping in pairs(config.get().mapping) do
|
||||
if keymap.equals(key, keys) and mapping[mode] then
|
||||
return mapping[mode](fallback)
|
||||
end
|
||||
end
|
||||
|
||||
--Commit character. NOTE: This has a lot of cmp specific implementation to make more user-friendly.
|
||||
local chars = keymap.t(keys)
|
||||
local e = self.view:get_active_entry()
|
||||
if e and vim.tbl_contains(config.get().confirmation.get_commit_characters(e:get_commit_characters()), chars) then
|
||||
local is_printable = char.is_printable(string.byte(chars, 1))
|
||||
self:confirm(e, {
|
||||
behavior = is_printable and 'insert' or 'replace',
|
||||
}, function()
|
||||
local ctx = self:get_context()
|
||||
local word = e:get_word()
|
||||
if string.sub(ctx.cursor_before_line, -#word, ctx.cursor.col - 1) == word and is_printable then
|
||||
fallback()
|
||||
else
|
||||
self:reset()
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
fallback()
|
||||
end
|
||||
|
||||
---Prepare completion
|
||||
core.prepare = function(self)
|
||||
for keys, mapping in pairs(config.get().mapping) do
|
||||
for mode in pairs(mapping) do
|
||||
keymap.listen(mode, keys, function(...)
|
||||
self:on_keymap(...)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Check auto-completion
|
||||
core.on_change = function(self, trigger_event)
|
||||
local ignore = false
|
||||
ignore = ignore or self.suspending
|
||||
ignore = ignore or (vim.fn.pumvisible() == 1 and (vim.v.completed_item).word)
|
||||
ignore = ignore or not self.view:ready()
|
||||
if ignore then
|
||||
self:get_context({ reason = types.cmp.ContextReason.Auto })
|
||||
return
|
||||
end
|
||||
|
||||
self:autoindent(trigger_event, function()
|
||||
local ctx = self:get_context({ reason = types.cmp.ContextReason.Auto })
|
||||
debug.log(('ctx: `%s`'):format(ctx.cursor_before_line))
|
||||
if ctx:changed(ctx.prev_context) then
|
||||
self.view:on_change()
|
||||
debug.log('changed')
|
||||
|
||||
if vim.tbl_contains(config.get().completion.autocomplete or {}, trigger_event) then
|
||||
self:complete(ctx)
|
||||
else
|
||||
self.filter.timeout = THROTTLE_TIME
|
||||
self:filter()
|
||||
end
|
||||
else
|
||||
debug.log('unchanged')
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---Cursor moved.
|
||||
core.on_moved = function(self)
|
||||
local ignore = false
|
||||
ignore = ignore or self.suspending
|
||||
ignore = ignore or (vim.fn.pumvisible() == 1 and (vim.v.completed_item).word)
|
||||
ignore = ignore or not self.view:visible()
|
||||
if ignore then
|
||||
return
|
||||
end
|
||||
self:filter()
|
||||
end
|
||||
|
||||
---Check autoindent
|
||||
---@param trigger_event cmp.TriggerEvent
|
||||
---@param callback function
|
||||
core.autoindent = function(self, trigger_event, callback)
|
||||
if trigger_event ~= types.cmp.TriggerEvent.TextChanged then
|
||||
return callback()
|
||||
end
|
||||
if not api.is_insert_mode() then
|
||||
return callback()
|
||||
end
|
||||
|
||||
-- Check prefix
|
||||
local cursor_before_line = api.get_cursor_before_line()
|
||||
local prefix = pattern.matchstr('[^[:blank:]]\\+$', cursor_before_line) or ''
|
||||
if #prefix == 0 then
|
||||
return callback()
|
||||
end
|
||||
|
||||
-- Scan indentkeys.
|
||||
for _, key in ipairs(vim.split(vim.bo.indentkeys, ',')) do
|
||||
if vim.tbl_contains({ '=' .. prefix, '0=' .. prefix }, key) then
|
||||
local release = self:suspend()
|
||||
vim.schedule(function() -- Check autoindent already applied.
|
||||
if cursor_before_line == api.get_cursor_before_line() then
|
||||
feedkeys.call(keymap.autoindent(), 'n', function()
|
||||
release()
|
||||
callback()
|
||||
end)
|
||||
else
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- indentkeys does not matched.
|
||||
callback()
|
||||
end
|
||||
|
||||
---Invoke completion
|
||||
---@param ctx cmp.Context
|
||||
core.complete = function(self, ctx)
|
||||
if not api.is_suitable_mode() then
|
||||
return
|
||||
end
|
||||
self:set_context(ctx)
|
||||
|
||||
for _, s in ipairs(self:get_sources({ source.SourceStatus.WAITING, source.SourceStatus.COMPLETED })) do
|
||||
s:complete(
|
||||
ctx,
|
||||
(function(src)
|
||||
local callback
|
||||
callback = function()
|
||||
local new = context.new(ctx)
|
||||
if new:changed(new.prev_context) and ctx == self.context then
|
||||
src:complete(new, callback)
|
||||
else
|
||||
self.filter.stop()
|
||||
self.filter.timeout = DEBOUNCE_TIME
|
||||
self:filter()
|
||||
end
|
||||
end
|
||||
return callback
|
||||
end)(s)
|
||||
)
|
||||
end
|
||||
|
||||
self.filter.timeout = THROTTLE_TIME
|
||||
self:filter()
|
||||
end
|
||||
|
||||
---Update completion menu
|
||||
core.filter = async.throttle(
|
||||
vim.schedule_wrap(function(self)
|
||||
if not api.is_suitable_mode() then
|
||||
return
|
||||
end
|
||||
if self.view:get_active_entry() ~= nil then
|
||||
return
|
||||
end
|
||||
local ctx = self:get_context()
|
||||
|
||||
-- To wait for processing source for that's timeout.
|
||||
local sources = {}
|
||||
for _, s in ipairs(self:get_sources({ source.SourceStatus.FETCHING, source.SourceStatus.COMPLETED })) do
|
||||
local time = SOURCE_TIMEOUT - s:get_fetching_time()
|
||||
if not s.incomplete and time > 0 then
|
||||
if #sources == 0 then
|
||||
self.filter.stop()
|
||||
self.filter.timeout = time + 1
|
||||
self:filter()
|
||||
return
|
||||
end
|
||||
break
|
||||
end
|
||||
table.insert(sources, s)
|
||||
end
|
||||
self.filter.timeout = THROTTLE_TIME
|
||||
|
||||
self.view:open(ctx, sources)
|
||||
end),
|
||||
THROTTLE_TIME
|
||||
)
|
||||
|
||||
---Confirm completion.
|
||||
---@param e cmp.Entry
|
||||
---@param option cmp.ConfirmOption
|
||||
---@param callback function
|
||||
core.confirm = function(self, e, option, callback)
|
||||
if not (e and not e.confirmed) then
|
||||
return callback()
|
||||
end
|
||||
e.confirmed = true
|
||||
|
||||
debug.log('entry.confirm', e:get_completion_item())
|
||||
|
||||
local release = self:suspend()
|
||||
|
||||
-- Close menus.
|
||||
self.view:close()
|
||||
|
||||
feedkeys.call('', 'n', function()
|
||||
local ctx = context.new()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor.character - vim.str_utfindex(ctx.cursor_line, e:get_offset() - 1)))
|
||||
table.insert(keys, e:get_word())
|
||||
table.insert(keys, keymap.undobreak())
|
||||
feedkeys.call(table.concat(keys, ''), 'int')
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
local ctx = context.new()
|
||||
if api.is_cmdline_mode() then
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor.character - vim.str_utfindex(ctx.cursor_line, e:get_offset() - 1)))
|
||||
table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset()))
|
||||
feedkeys.call(table.concat(keys, ''), 'int')
|
||||
else
|
||||
vim.api.nvim_buf_set_text(0, ctx.cursor.row - 1, e:get_offset() - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, {
|
||||
string.sub(e.context.cursor_before_line, e:get_offset()),
|
||||
})
|
||||
vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 })
|
||||
end
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then
|
||||
local pre = context.new()
|
||||
e:resolve(function()
|
||||
local new = context.new()
|
||||
local text_edits = misc.safe(e:get_completion_item().additionalTextEdits) or {}
|
||||
if #text_edits == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local has_cursor_line_text_edit = (function()
|
||||
local minrow = math.min(pre.cursor.row, new.cursor.row)
|
||||
local maxrow = math.max(pre.cursor.row, new.cursor.row)
|
||||
for _, te in ipairs(text_edits) do
|
||||
local srow = te.range.start.line + 1
|
||||
local erow = te.range['end'].line + 1
|
||||
if srow <= minrow and maxrow <= erow then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end)()
|
||||
if has_cursor_line_text_edit then
|
||||
return
|
||||
end
|
||||
vim.fn['cmp#apply_text_edits'](new.bufnr, text_edits)
|
||||
end)
|
||||
else
|
||||
vim.fn['cmp#apply_text_edits'](vim.api.nvim_get_current_buf(), e:get_completion_item().additionalTextEdits)
|
||||
end
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
local ctx = context.new()
|
||||
local completion_item = misc.copy(e:get_completion_item())
|
||||
if not misc.safe(completion_item.textEdit) then
|
||||
completion_item.textEdit = {}
|
||||
completion_item.textEdit.newText = misc.safe(completion_item.insertText) or completion_item.word or completion_item.label
|
||||
end
|
||||
local behavior = option.behavior or config.get().confirmation.default_behavior
|
||||
if behavior == types.cmp.ConfirmBehavior.Replace then
|
||||
completion_item.textEdit.range = e:get_replace_range()
|
||||
else
|
||||
completion_item.textEdit.range = e:get_insert_range()
|
||||
end
|
||||
|
||||
local diff_before = e.context.cursor.character - completion_item.textEdit.range.start.character
|
||||
local diff_after = completion_item.textEdit.range['end'].character - e.context.cursor.character
|
||||
local new_text = completion_item.textEdit.newText
|
||||
|
||||
if api.is_insert_mode() then
|
||||
local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet
|
||||
completion_item.textEdit.range.start.line = ctx.cursor.line
|
||||
completion_item.textEdit.range.start.character = ctx.cursor.character - diff_before
|
||||
completion_item.textEdit.range['end'].line = ctx.cursor.line
|
||||
completion_item.textEdit.range['end'].character = ctx.cursor.character + diff_after
|
||||
if is_snippet then
|
||||
completion_item.textEdit.newText = ''
|
||||
end
|
||||
vim.fn['cmp#apply_text_edits'](ctx.bufnr, { completion_item.textEdit })
|
||||
local texts = vim.split(completion_item.textEdit.newText, '\n')
|
||||
local position = completion_item.textEdit.range.start
|
||||
position.line = position.line + (#texts - 1)
|
||||
if #texts == 1 then
|
||||
position.character = position.character + vim.str_utfindex(texts[1])
|
||||
else
|
||||
position.character = vim.str_utfindex(texts[#texts])
|
||||
end
|
||||
local pos = types.lsp.Position.to_vim(0, position)
|
||||
vim.api.nvim_win_set_cursor(0, { pos.row, pos.col - 1 })
|
||||
if is_snippet then
|
||||
config.get().snippet.expand({
|
||||
body = new_text,
|
||||
insert_text_mode = completion_item.insertTextMode,
|
||||
})
|
||||
end
|
||||
else
|
||||
local keys = {}
|
||||
table.insert(keys, string.rep(keymap.t('<BS>'), diff_before))
|
||||
table.insert(keys, string.rep(keymap.t('<Del>'), diff_after))
|
||||
table.insert(keys, new_text)
|
||||
feedkeys.call(table.concat(keys, ''), 'int')
|
||||
end
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
e:execute(vim.schedule_wrap(function()
|
||||
release()
|
||||
self.event:emit('confirm_done', e)
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end))
|
||||
end)
|
||||
end
|
||||
|
||||
---Reset current completion state
|
||||
core.reset = function(self)
|
||||
for _, s in pairs(self.sources) do
|
||||
s:reset()
|
||||
end
|
||||
self:get_context() -- To prevent new event
|
||||
end
|
||||
|
||||
return core
|
158
bundle/nvim-cmp/lua/cmp/core_spec.lua
Normal file
158
bundle/nvim-cmp/lua/cmp/core_spec.lua
Normal file
@ -0,0 +1,158 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local types = require('cmp.types')
|
||||
local core = require('cmp.core')
|
||||
local source = require('cmp.source')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
describe('cmp.core', function()
|
||||
describe('confirm', function()
|
||||
local confirm = function(request, filter, completion_item)
|
||||
local c = core.new()
|
||||
local s = source.new('spec', {
|
||||
complete = function(_, _, callback)
|
||||
callback({ completion_item })
|
||||
end,
|
||||
})
|
||||
c:register_source(s)
|
||||
feedkeys.call(request, 'n', function()
|
||||
c:complete(c:get_context({ reason = types.cmp.ContextReason.Manual }))
|
||||
vim.wait(5000, function()
|
||||
return #c.sources[s.id].entries > 0
|
||||
end)
|
||||
end)
|
||||
feedkeys.call(filter, 'n', function()
|
||||
c:confirm(c.sources[s.id].entries[1], {})
|
||||
end)
|
||||
local state = {}
|
||||
feedkeys.call('', 'x', function()
|
||||
feedkeys.call('', 'n', function()
|
||||
if api.is_cmdline_mode() then
|
||||
state.buffer = { api.get_current_line() }
|
||||
else
|
||||
state.buffer = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
end
|
||||
state.cursor = api.get_cursor()
|
||||
end)
|
||||
end)
|
||||
return state
|
||||
end
|
||||
|
||||
describe('insert-mode', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('label', function()
|
||||
local state = confirm('iA', 'IU', {
|
||||
label = 'AIUEO',
|
||||
})
|
||||
assert.are.same(state.buffer, { 'AIUEO' })
|
||||
assert.are.same(state.cursor, { 1, 5 })
|
||||
end)
|
||||
|
||||
it('insertText', function()
|
||||
local state = confirm('iA', 'IU', {
|
||||
label = 'AIUEO',
|
||||
insertText = '_AIUEO_',
|
||||
})
|
||||
assert.are.same(state.buffer, { '_AIUEO_' })
|
||||
assert.are.same(state.cursor, { 1, 7 })
|
||||
end)
|
||||
|
||||
it('textEdit', function()
|
||||
local state = confirm(keymap.t('i***AEO***<Left><Left><Left><Left><Left>'), 'IU', {
|
||||
label = 'AIUEO',
|
||||
textEdit = {
|
||||
range = {
|
||||
start = {
|
||||
line = 0,
|
||||
character = 3,
|
||||
},
|
||||
['end'] = {
|
||||
line = 0,
|
||||
character = 6,
|
||||
},
|
||||
},
|
||||
newText = 'foo\nbar\nbaz',
|
||||
},
|
||||
})
|
||||
assert.are.same(state.buffer, { '***foo', 'bar', 'baz***' })
|
||||
assert.are.same(state.cursor, { 3, 3 })
|
||||
end)
|
||||
|
||||
it('insertText & snippet', function()
|
||||
local state = confirm('iA', 'IU', {
|
||||
label = 'AIUEO',
|
||||
insertText = 'AIUEO($0)',
|
||||
insertTextFormat = types.lsp.InsertTextFormat.Snippet,
|
||||
})
|
||||
assert.are.same(state.buffer, { 'AIUEO()' })
|
||||
assert.are.same(state.cursor, { 1, 6 })
|
||||
end)
|
||||
|
||||
it('textEdit & snippet', function()
|
||||
local state = confirm(keymap.t('i***AEO***<Left><Left><Left><Left><Left>'), 'IU', {
|
||||
label = 'AIUEO',
|
||||
insertTextFormat = types.lsp.InsertTextFormat.Snippet,
|
||||
textEdit = {
|
||||
range = {
|
||||
start = {
|
||||
line = 0,
|
||||
character = 3,
|
||||
},
|
||||
['end'] = {
|
||||
line = 0,
|
||||
character = 6,
|
||||
},
|
||||
},
|
||||
newText = 'foo\nba$0r\nbaz',
|
||||
},
|
||||
})
|
||||
assert.are.same(state.buffer, { '***foo', 'bar', 'baz***' })
|
||||
assert.are.same(state.cursor, { 2, 2 })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('cmdline-mode', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('label', function()
|
||||
local state = confirm(':A', 'IU', {
|
||||
label = 'AIUEO',
|
||||
})
|
||||
assert.are.same(state.buffer, { 'AIUEO' })
|
||||
assert.are.same(state.cursor[2], 5)
|
||||
end)
|
||||
|
||||
it('insertText', function()
|
||||
local state = confirm(':A', 'IU', {
|
||||
label = 'AIUEO',
|
||||
insertText = '_AIUEO_',
|
||||
})
|
||||
assert.are.same(state.buffer, { '_AIUEO_' })
|
||||
assert.are.same(state.cursor[2], 7)
|
||||
end)
|
||||
|
||||
it('textEdit', function()
|
||||
local state = confirm(keymap.t(':***AEO***<Left><Left><Left><Left><Left>'), 'IU', {
|
||||
label = 'AIUEO',
|
||||
textEdit = {
|
||||
range = {
|
||||
start = {
|
||||
line = 0,
|
||||
character = 3,
|
||||
},
|
||||
['end'] = {
|
||||
line = 0,
|
||||
character = 6,
|
||||
},
|
||||
},
|
||||
newText = 'foobarbaz',
|
||||
},
|
||||
})
|
||||
assert.are.same(state.buffer, { '***foobarbaz***' })
|
||||
assert.are.same(state.cursor[2], 12)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
430
bundle/nvim-cmp/lua/cmp/entry.lua
Normal file
430
bundle/nvim-cmp/lua/cmp/entry.lua
Normal file
@ -0,0 +1,430 @@
|
||||
local cache = require('cmp.utils.cache')
|
||||
local char = require('cmp.utils.char')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local str = require('cmp.utils.str')
|
||||
local config = require('cmp.config')
|
||||
local types = require('cmp.types')
|
||||
local matcher = require('cmp.matcher')
|
||||
|
||||
---@class cmp.Entry
|
||||
---@field public id number
|
||||
---@field public cache cmp.Cache
|
||||
---@field public match_cache cmp.Cache
|
||||
---@field public score number
|
||||
---@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_insert_range lsp.Range
|
||||
---@field public source_replace_range lsp.Range
|
||||
---@field public completion_item lsp.CompletionItem
|
||||
---@field public resolved_completion_item lsp.CompletionItem|nil
|
||||
---@field public resolved_callbacks fun()[]
|
||||
---@field public resolving boolean
|
||||
---@field public confirmed boolean
|
||||
local entry = {}
|
||||
|
||||
---Create new entry
|
||||
---@param ctx cmp.Context
|
||||
---@param source cmp.Source
|
||||
---@param completion_item lsp.CompletionItem
|
||||
---@return cmp.Entry
|
||||
entry.new = function(ctx, source, completion_item)
|
||||
local self = setmetatable({}, { __index = entry })
|
||||
self.id = misc.id('entry.new')
|
||||
self.cache = cache.new()
|
||||
self.match_cache = cache.new()
|
||||
self.score = 0
|
||||
self.exact = false
|
||||
self.matches = {}
|
||||
self.context = ctx
|
||||
self.source = source
|
||||
self.source_offset = source.request_offset
|
||||
self.source_insert_range = source:get_default_insert_range()
|
||||
self.source_replace_range = source:get_default_replace_range()
|
||||
self.completion_item = completion_item
|
||||
self.resolved_completion_item = nil
|
||||
self.resolved_callbacks = {}
|
||||
self.resolving = false
|
||||
self.confirmed = false
|
||||
return self
|
||||
end
|
||||
|
||||
---Make offset value
|
||||
---@return number
|
||||
entry.get_offset = function(self)
|
||||
return self.cache:ensure('get_offset', function()
|
||||
local offset = self.source_offset
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
local range = misc.safe(self.completion_item.textEdit.insert) or misc.safe(self.completion_item.textEdit.range)
|
||||
if range then
|
||||
local c = misc.to_vimindex(self.context.cursor_line, range.start.character)
|
||||
for idx = c, self.source_offset do
|
||||
if not char.is_white(string.byte(self.context.cursor_line, idx)) then
|
||||
offset = idx
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- NOTE
|
||||
-- The VSCode does not implement this but it's useful if the server does not care about word patterns.
|
||||
-- We should care about this performance.
|
||||
local word = self:get_word()
|
||||
for idx = self.source_offset - 1, self.source_offset - #word, -1 do
|
||||
if char.is_semantic_index(self.context.cursor_line, idx) then
|
||||
local c = string.byte(self.context.cursor_line, idx)
|
||||
if char.is_white(c) then
|
||||
break
|
||||
end
|
||||
local match = true
|
||||
for i = 1, self.source_offset - idx do
|
||||
local c1 = string.byte(word, i)
|
||||
local c2 = string.byte(self.context.cursor_line, idx + i - 1)
|
||||
if not c1 or not c2 or c1 ~= c2 then
|
||||
match = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if match then
|
||||
offset = math.min(offset, idx)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return offset
|
||||
end)
|
||||
end
|
||||
|
||||
---Create word for vim.CompletedItem
|
||||
---@return string
|
||||
entry.get_word = function(self)
|
||||
return self.cache:ensure('get_word', function()
|
||||
--NOTE: This is nvim-cmp specific implementation.
|
||||
if misc.safe(self.completion_item.word) then
|
||||
return self.completion_item.word
|
||||
end
|
||||
|
||||
local word
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
word = str.trim(self.completion_item.textEdit.newText)
|
||||
local overwrite = self:get_overwrite()
|
||||
if 0 < overwrite[2] or self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
word = str.get_word(word, string.byte(self.context.cursor_after_line, 1))
|
||||
end
|
||||
elseif misc.safe(self.completion_item.insertText) then
|
||||
word = str.trim(self.completion_item.insertText)
|
||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
word = str.get_word(word)
|
||||
end
|
||||
else
|
||||
word = str.trim(self.completion_item.label)
|
||||
end
|
||||
return str.oneline(word)
|
||||
end)
|
||||
end
|
||||
|
||||
---Get overwrite information
|
||||
---@return number, number
|
||||
entry.get_overwrite = function(self)
|
||||
return self.cache:ensure('get_overwrite', function()
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
local r = misc.safe(self.completion_item.textEdit.insert) or misc.safe(self.completion_item.textEdit.range)
|
||||
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
|
||||
return { 0, 0 }
|
||||
end)
|
||||
end
|
||||
|
||||
---Create filter text
|
||||
---@return string
|
||||
entry.get_filter_text = function(self)
|
||||
return self.cache:ensure('get_filter_text', function()
|
||||
local word
|
||||
if misc.safe(self.completion_item.filterText) then
|
||||
word = self.completion_item.filterText
|
||||
else
|
||||
word = str.trim(self.completion_item.label)
|
||||
end
|
||||
|
||||
-- @see https://github.com/clangd/clangd/issues/815
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
local diff = self.source_offset - self:get_offset()
|
||||
if diff > 0 then
|
||||
if char.is_symbol(string.byte(self.context.cursor_line, self:get_offset())) then
|
||||
local prefix = string.sub(self.context.cursor_line, self:get_offset(), self:get_offset() + diff)
|
||||
if string.find(word, prefix, 1, true) ~= 1 then
|
||||
word = prefix .. word
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return word
|
||||
end)
|
||||
end
|
||||
|
||||
---Get LSP's insert text
|
||||
---@return string
|
||||
entry.get_insert_text = function(self)
|
||||
return self.cache:ensure('get_insert_text', function()
|
||||
local word
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
word = str.trim(self.completion_item.textEdit.newText)
|
||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
||||
end
|
||||
elseif misc.safe(self.completion_item.insertText) then
|
||||
word = str.trim(self.completion_item.insertText)
|
||||
if self.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
||||
end
|
||||
else
|
||||
word = str.trim(self.completion_item.label)
|
||||
end
|
||||
return word
|
||||
end)
|
||||
end
|
||||
|
||||
---Return the item is deprecated or not.
|
||||
---@return boolean
|
||||
entry.is_deprecated = function(self)
|
||||
return self.completion_item.deprecated or vim.tbl_contains(self.completion_item.tags or {}, types.lsp.CompletionItemTag.Deprecated)
|
||||
end
|
||||
|
||||
---Return view information.
|
||||
---@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)
|
||||
local item = self:get_vim_item(suggest_offset)
|
||||
return self.cache:ensure({ 'get_view', self.resolved_completion_item and 1 or 0 }, function()
|
||||
local view = {}
|
||||
view.abbr = {}
|
||||
view.abbr.text = item.abbr or ''
|
||||
view.abbr.bytes = #view.abbr.text
|
||||
view.abbr.width = vim.str_utfindex(view.abbr.text)
|
||||
view.abbr.hl_group = self:is_deprecated() and 'CmpItemAbbrDeprecated' or 'CmpItemAbbr'
|
||||
view.kind = {}
|
||||
view.kind.text = item.kind or ''
|
||||
view.kind.bytes = #view.kind.text
|
||||
view.kind.width = vim.str_utfindex(view.kind.text)
|
||||
view.kind.hl_group = 'CmpItemKind'
|
||||
view.menu = {}
|
||||
view.menu.text = item.menu or ''
|
||||
view.menu.bytes = #view.menu.text
|
||||
view.menu.width = vim.str_utfindex(view.menu.text)
|
||||
view.menu.hl_group = 'CmpItemMenu'
|
||||
view.dup = item.dup
|
||||
return view
|
||||
end)
|
||||
end
|
||||
|
||||
---Make vim.CompletedItem
|
||||
---@param suggest_offset number
|
||||
---@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()
|
||||
local completion_item = self:get_completion_item()
|
||||
local word = self:get_word()
|
||||
local abbr = str.oneline(completion_item.label)
|
||||
|
||||
-- ~ indicator
|
||||
if #(misc.safe(completion_item.additionalTextEdits) or {}) > 0 then
|
||||
abbr = abbr .. '~'
|
||||
elseif completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
local insert_text = self:get_insert_text()
|
||||
if word ~= insert_text then
|
||||
abbr = abbr .. '~'
|
||||
end
|
||||
end
|
||||
|
||||
-- append delta text
|
||||
if suggest_offset < self:get_offset() then
|
||||
word = string.sub(self.context.cursor_before_line, suggest_offset, self:get_offset() - 1) .. word
|
||||
end
|
||||
|
||||
-- labelDetails.
|
||||
local menu = nil
|
||||
if misc.safe(completion_item.labelDetails) then
|
||||
menu = ''
|
||||
if misc.safe(completion_item.labelDetails.detail) then
|
||||
menu = menu .. completion_item.labelDetails.detail
|
||||
end
|
||||
if misc.safe(completion_item.labelDetails.description) then
|
||||
menu = menu .. completion_item.labelDetails.description
|
||||
end
|
||||
end
|
||||
|
||||
-- remove duplicated string.
|
||||
for i = 1, #word - 1 do
|
||||
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
|
||||
word = string.sub(word, 1, i - 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local vim_item = {
|
||||
word = word,
|
||||
abbr = abbr,
|
||||
kind = types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
|
||||
menu = menu,
|
||||
dup = self.completion_item.dup or 1,
|
||||
}
|
||||
if config.get().formatting.format then
|
||||
vim_item = config.get().formatting.format(self, vim_item)
|
||||
end
|
||||
vim_item.word = str.oneline(vim_item.word or '')
|
||||
vim_item.abbr = str.oneline(vim_item.abbr or '')
|
||||
vim_item.kind = str.oneline(vim_item.kind or '')
|
||||
vim_item.menu = str.oneline(vim_item.menu or '')
|
||||
vim_item.equal = 1
|
||||
vim_item.empty = 1
|
||||
|
||||
return vim_item
|
||||
end)
|
||||
end
|
||||
|
||||
---Get commit characters
|
||||
---@return string[]
|
||||
entry.get_commit_characters = function(self)
|
||||
return misc.safe(self:get_completion_item().commitCharacters) or {}
|
||||
end
|
||||
|
||||
---Return insert range
|
||||
---@return lsp.Range|nil
|
||||
entry.get_insert_range = function(self)
|
||||
local insert_range
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
if misc.safe(self.completion_item.textEdit.insert) then
|
||||
insert_range = self.completion_item.textEdit.insert
|
||||
else
|
||||
insert_range = self.completion_item.textEdit.range
|
||||
end
|
||||
else
|
||||
insert_range = {
|
||||
start = {
|
||||
line = self.context.cursor.row - 1,
|
||||
character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), self.source_insert_range.start.character),
|
||||
},
|
||||
['end'] = self.source_insert_range['end'],
|
||||
}
|
||||
end
|
||||
return insert_range
|
||||
end
|
||||
|
||||
---Return replace range
|
||||
---@return lsp.Range|nil
|
||||
entry.get_replace_range = function(self)
|
||||
return self.cache:ensure('get_replace_range', function()
|
||||
local replace_range
|
||||
if misc.safe(self.completion_item.textEdit) then
|
||||
if misc.safe(self.completion_item.textEdit.replace) then
|
||||
replace_range = self.completion_item.textEdit.replace
|
||||
else
|
||||
replace_range = self.completion_item.textEdit.range
|
||||
end
|
||||
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),
|
||||
},
|
||||
['end'] = self.source_replace_range['end'],
|
||||
}
|
||||
end
|
||||
return replace_range
|
||||
end)
|
||||
end
|
||||
|
||||
---Match line.
|
||||
---@param input string
|
||||
---@return { score: number, matches: table[] }
|
||||
entry.match = function(self, input)
|
||||
return self.match_cache:ensure(input, function()
|
||||
local score, matches, _
|
||||
score, matches = matcher.match(input, self:get_filter_text(), { self:get_word(), self:get_completion_item().label })
|
||||
if self:get_filter_text() ~= self:get_completion_item().label then
|
||||
_, matches = matcher.match(input, self:get_completion_item().label, { self:get_word() })
|
||||
end
|
||||
return { score = score, matches = matches }
|
||||
end)
|
||||
end
|
||||
|
||||
---Get resolved completion item if possible.
|
||||
---@return lsp.CompletionItem
|
||||
entry.get_completion_item = function(self)
|
||||
return self.cache:ensure({ 'get_completion_item', (self.resolved_completion_item and 1 or 0) }, function()
|
||||
if self.resolved_completion_item then
|
||||
local completion_item = misc.copy(self.completion_item)
|
||||
completion_item.detail = self.resolved_completion_item.detail or completion_item.detail
|
||||
completion_item.documentation = self.resolved_completion_item.documentation or completion_item.documentation
|
||||
completion_item.additionalTextEdits = self.resolved_completion_item.additionalTextEdits or completion_item.additionalTextEdits
|
||||
return completion_item
|
||||
end
|
||||
return self.completion_item
|
||||
end)
|
||||
end
|
||||
|
||||
---Create documentation
|
||||
---@return string
|
||||
entry.get_documentation = function(self)
|
||||
local item = self:get_completion_item()
|
||||
|
||||
local documents = {}
|
||||
|
||||
-- detail
|
||||
if misc.safe(item.detail) and item.detail ~= '' then
|
||||
table.insert(documents, {
|
||||
kind = types.lsp.MarkupKind.Markdown,
|
||||
value = ('```%s\n%s\n```'):format(self.context.filetype, str.trim(item.detail)),
|
||||
})
|
||||
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)
|
||||
end
|
||||
|
||||
return vim.lsp.util.convert_input_to_markdown_lines(documents)
|
||||
end
|
||||
|
||||
---Get completion item kind
|
||||
---@return lsp.CompletionItemKind
|
||||
entry.get_kind = function(self)
|
||||
return misc.safe(self.completion_item.kind) or types.lsp.CompletionItemKind.Text
|
||||
end
|
||||
|
||||
---Execute completion item's command.
|
||||
---@param callback fun()
|
||||
entry.execute = function(self, callback)
|
||||
self.source:execute(self:get_completion_item(), callback)
|
||||
end
|
||||
|
||||
---Resolve completion item.
|
||||
---@param callback fun()
|
||||
entry.resolve = function(self, callback)
|
||||
if self.resolved_completion_item then
|
||||
return callback()
|
||||
end
|
||||
table.insert(self.resolved_callbacks, callback)
|
||||
|
||||
if not self.resolving then
|
||||
self.resolving = true
|
||||
self.source:resolve(self.completion_item, function(completion_item)
|
||||
self.resolved_completion_item = misc.safe(completion_item) or self.completion_item
|
||||
for _, c in ipairs(self.resolved_callbacks) do
|
||||
c()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return entry
|
281
bundle/nvim-cmp/lua/cmp/entry_spec.lua
Normal file
281
bundle/nvim-cmp/lua/cmp/entry_spec.lua
Normal file
@ -0,0 +1,281 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
|
||||
local entry = require('cmp.entry')
|
||||
|
||||
describe('entry', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('one char', function()
|
||||
local state = spec.state('@.', 1, 3)
|
||||
state.input('@')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
label = '@',
|
||||
})
|
||||
assert.are.equal(e:get_offset(), 3)
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '@')
|
||||
end)
|
||||
|
||||
it('word length (no fix)', function()
|
||||
local state = spec.state('a.b', 1, 4)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
label = 'b',
|
||||
})
|
||||
assert.are.equal(e:get_offset(), 5)
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'b')
|
||||
end)
|
||||
|
||||
it('word length (fix)', function()
|
||||
local state = spec.state('a.b', 1, 4)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
label = 'b.',
|
||||
})
|
||||
assert.are.equal(e:get_offset(), 3)
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'b.')
|
||||
end)
|
||||
|
||||
it('semantic index (no fix)', function()
|
||||
local state = spec.state('a.bc', 1, 5)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
label = 'c.',
|
||||
})
|
||||
assert.are.equal(e:get_offset(), 6)
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'c.')
|
||||
end)
|
||||
|
||||
it('semantic index (fix)', function()
|
||||
local state = spec.state('a.bc', 1, 5)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
label = 'bc.',
|
||||
})
|
||||
assert.are.equal(e:get_offset(), 3)
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'bc.')
|
||||
end)
|
||||
|
||||
it('[vscode-html-language-server] 1', function()
|
||||
local state = spec.state(' </>', 1, 7)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
label = '/div',
|
||||
textEdit = {
|
||||
range = {
|
||||
start = {
|
||||
line = 0,
|
||||
character = 0,
|
||||
},
|
||||
['end'] = {
|
||||
line = 0,
|
||||
character = 6,
|
||||
},
|
||||
},
|
||||
newText = ' </div',
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_offset(), 5)
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '</div')
|
||||
end)
|
||||
|
||||
it('[clangd] 1', function()
|
||||
--NOTE: clangd does not return `.foo` as filterText but we should care about it.
|
||||
--nvim-cmp does care it by special handling in entry.lua.
|
||||
local state = spec.state('foo', 1, 4)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
insertText = '->foo',
|
||||
label = ' foo',
|
||||
textEdit = {
|
||||
newText = '->foo',
|
||||
range = {
|
||||
start = {
|
||||
character = 3,
|
||||
line = 1,
|
||||
},
|
||||
['end'] = {
|
||||
character = 4,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(4).word, '->foo')
|
||||
assert.are.equal(e:get_filter_text(), '.foo')
|
||||
end)
|
||||
|
||||
it('[typescript-language-server] 1', function()
|
||||
local state = spec.state('Promise.resolve()', 1, 18)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
label = 'catch',
|
||||
})
|
||||
-- The offset will be 18 in this situation because the server returns `[Symbol]` as candidate.
|
||||
assert.are.equal(e:get_vim_item(18).word, '.catch')
|
||||
assert.are.equal(e:get_filter_text(), 'catch')
|
||||
end)
|
||||
|
||||
it('[typescript-language-server] 2', function()
|
||||
local state = spec.state('Promise.resolve()', 1, 18)
|
||||
state.input('.')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
filterText = '.Symbol',
|
||||
label = 'Symbol',
|
||||
textEdit = {
|
||||
newText = '[Symbol]',
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 18,
|
||||
line = 0,
|
||||
},
|
||||
start = {
|
||||
character = 17,
|
||||
line = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(18).word, '[Symbol]')
|
||||
assert.are.equal(e:get_filter_text(), '.Symbol')
|
||||
end)
|
||||
|
||||
it('[lua-language-server] 1', function()
|
||||
local state = spec.state("local m = require'cmp.confi", 1, 28)
|
||||
local e
|
||||
|
||||
-- press g
|
||||
state.input('g')
|
||||
e = entry.new(state.manual(), state.source(), {
|
||||
insertTextFormat = 2,
|
||||
label = 'cmp.config',
|
||||
textEdit = {
|
||||
newText = 'cmp.config',
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 27,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 18,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(19).word, 'cmp.config')
|
||||
assert.are.equal(e:get_filter_text(), 'cmp.config')
|
||||
|
||||
-- press '
|
||||
state.input("'")
|
||||
e = entry.new(state.manual(), state.source(), {
|
||||
insertTextFormat = 2,
|
||||
label = 'cmp.config',
|
||||
textEdit = {
|
||||
newText = 'cmp.config',
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 27,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 18,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(19).word, 'cmp.config')
|
||||
assert.are.equal(e:get_filter_text(), 'cmp.config')
|
||||
end)
|
||||
|
||||
it('[lua-language-server] 2', function()
|
||||
local state = spec.state("local m = require'cmp.confi", 1, 28)
|
||||
local e
|
||||
|
||||
-- press g
|
||||
state.input('g')
|
||||
e = entry.new(state.manual(), state.source(), {
|
||||
insertTextFormat = 2,
|
||||
label = 'lua.cmp.config',
|
||||
textEdit = {
|
||||
newText = 'lua.cmp.config',
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 27,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 18,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(19).word, 'lua.cmp.config')
|
||||
assert.are.equal(e:get_filter_text(), 'lua.cmp.config')
|
||||
|
||||
-- press '
|
||||
state.input("'")
|
||||
e = entry.new(state.manual(), state.source(), {
|
||||
insertTextFormat = 2,
|
||||
label = 'lua.cmp.config',
|
||||
textEdit = {
|
||||
newText = 'lua.cmp.config',
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 27,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 18,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(19).word, 'lua.cmp.config')
|
||||
assert.are.equal(e:get_filter_text(), 'lua.cmp.config')
|
||||
end)
|
||||
|
||||
it('[intelephense] 1', function()
|
||||
local state = spec.state('\t\t', 1, 4)
|
||||
|
||||
-- press g
|
||||
state.input('$')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
kind = 6,
|
||||
label = '$this',
|
||||
sortText = '$this',
|
||||
textEdit = {
|
||||
newText = '$this',
|
||||
range = {
|
||||
['end'] = {
|
||||
character = 3,
|
||||
line = 1,
|
||||
},
|
||||
start = {
|
||||
character = 2,
|
||||
line = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '$this')
|
||||
assert.are.equal(e:get_filter_text(), '$this')
|
||||
end)
|
||||
|
||||
it('[#47] word should not contain \\n character', function()
|
||||
local state = spec.state('', 1, 1)
|
||||
|
||||
-- press g
|
||||
state.input('_')
|
||||
local e = entry.new(state.manual(), state.source(), {
|
||||
kind = 6,
|
||||
label = '__init__',
|
||||
insertTextFormat = 1,
|
||||
insertText = '__init__(self) -> None:\n pass',
|
||||
})
|
||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '__init__(self) -> None:')
|
||||
assert.are.equal(e:get_filter_text(), '__init__')
|
||||
end)
|
||||
end)
|
312
bundle/nvim-cmp/lua/cmp/init.lua
Normal file
312
bundle/nvim-cmp/lua/cmp/init.lua
Normal file
@ -0,0 +1,312 @@
|
||||
local core = require('cmp.core')
|
||||
local source = require('cmp.source')
|
||||
local config = require('cmp.config')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local autocmd = require('cmp.utils.autocmd')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local misc = require('cmp.utils.misc')
|
||||
|
||||
local cmp = {}
|
||||
|
||||
cmp.core = core.new()
|
||||
|
||||
---Expose types
|
||||
for k, v in pairs(require('cmp.types.cmp')) do
|
||||
cmp[k] = v
|
||||
end
|
||||
cmp.lsp = require('cmp.types.lsp')
|
||||
cmp.vim = require('cmp.types.vim')
|
||||
|
||||
---Export default config presets.
|
||||
cmp.config = {}
|
||||
cmp.config.disable = misc.none
|
||||
cmp.config.compare = require('cmp.config.compare')
|
||||
cmp.config.sources = require('cmp.config.sources')
|
||||
|
||||
---Expose event
|
||||
cmp.event = cmp.core.event
|
||||
|
||||
---Export mapping
|
||||
cmp.mapping = require('cmp.config.mapping')
|
||||
|
||||
---Register completion sources
|
||||
---@param name string
|
||||
---@param s cmp.Source
|
||||
---@return number
|
||||
cmp.register_source = function(name, s)
|
||||
local src = source.new(name, s)
|
||||
cmp.core:register_source(src)
|
||||
return src.id
|
||||
end
|
||||
|
||||
---Unregister completion source
|
||||
---@param id number
|
||||
cmp.unregister_source = function(id)
|
||||
cmp.core:unregister_source(id)
|
||||
end
|
||||
|
||||
---Get current configuration.
|
||||
---@return cmp.ConfigSchema
|
||||
cmp.get_config = function()
|
||||
return require('cmp.config').get()
|
||||
end
|
||||
|
||||
---Invoke completion manually
|
||||
cmp.complete = function()
|
||||
cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.Manual }))
|
||||
return true
|
||||
end
|
||||
|
||||
---Return view is visible or not.
|
||||
cmp.visible = function()
|
||||
return cmp.core.view:visible() or vim.fn.pumvisible() == 1
|
||||
end
|
||||
|
||||
---Get current selected entry or nil
|
||||
cmp.get_selected_entry = function()
|
||||
return cmp.core.view:get_selected_entry()
|
||||
end
|
||||
|
||||
---Get current active entry or nil
|
||||
cmp.get_active_entry = function()
|
||||
return cmp.core.view:get_active_entry()
|
||||
end
|
||||
|
||||
---Close current completion
|
||||
cmp.close = 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
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---Abort current completion
|
||||
cmp.abort = function()
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:abort()
|
||||
vim.schedule(release)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---Suspend completion.
|
||||
cmp.suspend = function()
|
||||
return cmp.core:suspend()
|
||||
end
|
||||
|
||||
---Select next item if possible
|
||||
cmp.select_next_item = function(option)
|
||||
option = option or {}
|
||||
|
||||
-- Hack: Ignore when executing macro.
|
||||
if vim.fn.reg_executing() ~= '' then
|
||||
return true
|
||||
end
|
||||
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:select_next_item(option)
|
||||
vim.schedule(release)
|
||||
return true
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
if (option.behavior or cmp.SelectBehavior.Insert) == cmp.SelectBehavior.Insert then
|
||||
feedkeys.call(keymap.t('<C-n>'), 'n')
|
||||
else
|
||||
feedkeys.call(keymap.t('<Down>'), 'n')
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Select prev item if possible
|
||||
cmp.select_prev_item = function(option)
|
||||
option = option or {}
|
||||
|
||||
-- Hack: Ignore when executing macro.
|
||||
if vim.fn.reg_executing() ~= '' then
|
||||
return true
|
||||
end
|
||||
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:select_prev_item(option)
|
||||
vim.schedule(release)
|
||||
return true
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
if (option.behavior or cmp.SelectBehavior.Insert) == cmp.SelectBehavior.Insert then
|
||||
feedkeys.call(keymap.t('<C-p>'), 'n')
|
||||
else
|
||||
feedkeys.call(keymap.t('<Up>'), 'n')
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Scrolling documentation window if possible
|
||||
cmp.scroll_docs = function(delta)
|
||||
if cmp.core.view:visible() then
|
||||
cmp.core.view:scroll_docs(delta)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---Confirm completion
|
||||
cmp.confirm = function(option, callback)
|
||||
option = option or {}
|
||||
callback = callback or function() end
|
||||
|
||||
-- Hack: Ignore when executing macro.
|
||||
if vim.fn.reg_executing() ~= '' then
|
||||
return true
|
||||
end
|
||||
|
||||
local e = cmp.core.view:get_selected_entry() or (option.select and cmp.core.view:get_first_entry() or nil)
|
||||
if e then
|
||||
cmp.core:confirm(e, {
|
||||
behavior = option.behavior,
|
||||
}, function()
|
||||
callback()
|
||||
cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.TriggerOnly }))
|
||||
end)
|
||||
return true
|
||||
else
|
||||
if vim.fn.complete_info({ 'selected' }).selected ~= -1 then
|
||||
feedkeys.call(keymap.t('<C-y>'), 'n')
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---Show status
|
||||
cmp.status = function()
|
||||
local kinds = {}
|
||||
kinds.available = {}
|
||||
kinds.unavailable = {}
|
||||
kinds.installed = {}
|
||||
kinds.invalid = {}
|
||||
local names = {}
|
||||
for _, s in pairs(cmp.core.sources) do
|
||||
names[s.name] = true
|
||||
|
||||
if config.get_source_config(s.name) then
|
||||
if s:is_available() then
|
||||
table.insert(kinds.available, s:get_debug_name())
|
||||
else
|
||||
table.insert(kinds.unavailable, s:get_debug_name())
|
||||
end
|
||||
else
|
||||
table.insert(kinds.installed, s:get_debug_name())
|
||||
end
|
||||
end
|
||||
for _, s in ipairs(config.get().sources) do
|
||||
if not names[s.name] then
|
||||
table.insert(kinds.invalid, s.name)
|
||||
end
|
||||
end
|
||||
|
||||
if #kinds.available > 0 then
|
||||
vim.api.nvim_echo({ { '\n', 'Normal' } }, false, {})
|
||||
vim.api.nvim_echo({ { '# ready source names\n', 'Special' } }, false, {})
|
||||
for _, name in ipairs(kinds.available) do
|
||||
vim.api.nvim_echo({ { ('- %s\n'):format(name), 'Normal' } }, false, {})
|
||||
end
|
||||
end
|
||||
|
||||
if #kinds.unavailable > 0 then
|
||||
vim.api.nvim_echo({ { '\n', 'Normal' } }, false, {})
|
||||
vim.api.nvim_echo({ { '# unavailable source names\n', 'Comment' } }, false, {})
|
||||
for _, name in ipairs(kinds.unavailable) do
|
||||
vim.api.nvim_echo({ { ('- %s\n'):format(name), 'Normal' } }, false, {})
|
||||
end
|
||||
end
|
||||
|
||||
if #kinds.installed > 0 then
|
||||
vim.api.nvim_echo({ { '\n', 'Normal' } }, false, {})
|
||||
vim.api.nvim_echo({ { '# unused source names\n', 'WarningMsg' } }, false, {})
|
||||
for _, name in ipairs(kinds.installed) do
|
||||
vim.api.nvim_echo({ { ('- %s\n'):format(name), 'Normal' } }, false, {})
|
||||
end
|
||||
end
|
||||
|
||||
if #kinds.invalid > 0 then
|
||||
vim.api.nvim_echo({ { '\n', 'Normal' } }, false, {})
|
||||
vim.api.nvim_echo({ { '# unknown source names\n', 'ErrorMsg' } }, false, {})
|
||||
for _, name in ipairs(kinds.invalid) do
|
||||
vim.api.nvim_echo({ { ('- %s\n'):format(name), 'Normal' } }, false, {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@type cmp.Setup
|
||||
cmp.setup = setmetatable({
|
||||
global = function(c)
|
||||
config.set_global(c)
|
||||
end,
|
||||
buffer = function(c)
|
||||
config.set_buffer(c, vim.api.nvim_get_current_buf())
|
||||
end,
|
||||
cmdline = function(type, c)
|
||||
config.set_cmdline(c, type)
|
||||
end,
|
||||
}, {
|
||||
__call = function(self, c)
|
||||
self.global(c)
|
||||
end,
|
||||
})
|
||||
|
||||
autocmd.subscribe('InsertEnter', function()
|
||||
feedkeys.call('', 'i', function()
|
||||
if config.enabled() then
|
||||
cmp.core:prepare()
|
||||
cmp.core:on_change('InsertEnter')
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
autocmd.subscribe('InsertLeave', function()
|
||||
cmp.core:reset()
|
||||
cmp.core.view:close()
|
||||
end)
|
||||
|
||||
autocmd.subscribe('CmdlineEnter', function()
|
||||
if config.enabled() then
|
||||
cmp.core:prepare()
|
||||
cmp.core:on_change('InsertEnter')
|
||||
end
|
||||
end)
|
||||
|
||||
autocmd.subscribe('CmdlineLeave', function()
|
||||
cmp.core:reset()
|
||||
cmp.core.view:close()
|
||||
end)
|
||||
|
||||
autocmd.subscribe('TextChanged', function()
|
||||
if config.enabled() then
|
||||
cmp.core:on_change('TextChanged')
|
||||
end
|
||||
end)
|
||||
|
||||
autocmd.subscribe('CursorMoved', function()
|
||||
if config.enabled() then
|
||||
cmp.core:on_moved()
|
||||
end
|
||||
end)
|
||||
|
||||
cmp.event:on('confirm_done', function(e)
|
||||
cmp.config.compare.recently_used:add_entry(e)
|
||||
end)
|
||||
|
||||
return cmp
|
292
bundle/nvim-cmp/lua/cmp/matcher.lua
Normal file
292
bundle/nvim-cmp/lua/cmp/matcher.lua
Normal file
@ -0,0 +1,292 @@
|
||||
local char = require('cmp.utils.char')
|
||||
|
||||
local matcher = {}
|
||||
|
||||
matcher.WORD_BOUNDALY_ORDER_FACTOR = 10
|
||||
|
||||
matcher.PREFIX_FACTOR = 8
|
||||
matcher.NOT_FUZZY_FACTOR = 6
|
||||
|
||||
---@type function
|
||||
matcher.debug = function(...)
|
||||
return ...
|
||||
end
|
||||
|
||||
--- score
|
||||
--
|
||||
-- ### The score
|
||||
--
|
||||
-- The `score` is `matched char count` generally.
|
||||
--
|
||||
-- But cmp will fix the score with some of the below points so the actual score is not `matched char count`.
|
||||
--
|
||||
-- 1. Word boundary order
|
||||
--
|
||||
-- cmp prefers the match that near by word-beggining.
|
||||
--
|
||||
-- 2. Strict case
|
||||
--
|
||||
-- cmp prefers strict match than ignorecase match.
|
||||
--
|
||||
--
|
||||
-- ### Matching specs.
|
||||
--
|
||||
-- 1. Prefix matching per word boundary
|
||||
--
|
||||
-- `bora` -> `border-radius` # imaginary score: 4
|
||||
-- ^^~~ ^^ ~~
|
||||
--
|
||||
-- 2. Try sequential match first
|
||||
--
|
||||
-- `woroff` -> `word_offset` # imaginary score: 6
|
||||
-- ^^^~~~ ^^^ ~~~
|
||||
--
|
||||
-- * The `woroff`'s second `o` should not match `word_offset`'s first `o`
|
||||
--
|
||||
-- 3. Prefer early word boundary
|
||||
--
|
||||
-- `call` -> `call` # imaginary score: 4.1
|
||||
-- ^^^^ ^^^^
|
||||
-- `call` -> `condition_all` # imaginary score: 4
|
||||
-- ^~~~ ^ ~~~
|
||||
--
|
||||
-- 4. Prefer strict match
|
||||
--
|
||||
-- `Buffer` -> `Buffer` # imaginary score: 6.1
|
||||
-- ^^^^^^ ^^^^^^
|
||||
-- `buffer` -> `Buffer` # imaginary score: 6
|
||||
-- ^^^^^^ ^^^^^^
|
||||
--
|
||||
-- 5. Use remaining characters for substring match
|
||||
--
|
||||
-- `fmodify` -> `fnamemodify` # imaginary score: 1
|
||||
-- ^~~~~~~ ^ ~~~~~~
|
||||
--
|
||||
-- 6. Avoid unexpected match detection
|
||||
--
|
||||
-- `candlesingle` -> candle#accept#single
|
||||
-- ^^^^^^~~~~~~ ^^^^^^ ~~~~~~
|
||||
--
|
||||
-- * The `accept`'s `a` should not match to `candle`'s `a`
|
||||
--
|
||||
---Match entry
|
||||
---@param input string
|
||||
---@param word string
|
||||
---@param words string[]
|
||||
---@return number
|
||||
matcher.match = function(input, word, words)
|
||||
-- Empty input
|
||||
if #input == 0 then
|
||||
return matcher.PREFIX_FACTOR + matcher.NOT_FUZZY_FACTOR, {}
|
||||
end
|
||||
|
||||
-- Ignore if input is long than word
|
||||
if #input > #word then
|
||||
return 0, {}
|
||||
end
|
||||
|
||||
--- Gather matched regions
|
||||
local matches = {}
|
||||
local input_start_index = 1
|
||||
local input_end_index = 1
|
||||
local word_index = 1
|
||||
local word_bound_index = 1
|
||||
while input_end_index <= #input and word_index <= #word do
|
||||
local m = matcher.find_match_region(input, input_start_index, input_end_index, word, word_index)
|
||||
if m and input_end_index <= m.input_match_end then
|
||||
m.index = word_bound_index
|
||||
input_start_index = m.input_match_start + 1
|
||||
input_end_index = m.input_match_end + 1
|
||||
word_index = char.get_next_semantic_index(word, m.word_match_end)
|
||||
table.insert(matches, m)
|
||||
else
|
||||
word_index = char.get_next_semantic_index(word, word_index)
|
||||
end
|
||||
word_bound_index = word_bound_index + 1
|
||||
end
|
||||
|
||||
if #matches == 0 then
|
||||
return 0, {}
|
||||
end
|
||||
|
||||
matcher.debug(word, matches)
|
||||
|
||||
-- Add prefix bonus
|
||||
local prefix = false
|
||||
if matches[1].input_match_start == 1 and matches[1].word_match_start == 1 then
|
||||
prefix = true
|
||||
else
|
||||
for _, w in ipairs(words or {}) do
|
||||
prefix = true
|
||||
local o = 1
|
||||
for i = matches[1].input_match_start, matches[1].input_match_end do
|
||||
if not char.match(string.byte(w, o), string.byte(input, i)) then
|
||||
prefix = false
|
||||
break
|
||||
end
|
||||
o = o + 1
|
||||
end
|
||||
if prefix then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Compute prefix match score
|
||||
local score = prefix and matcher.PREFIX_FACTOR or 0
|
||||
local offset = prefix and matches[1].index - 1 or 0
|
||||
local idx = 1
|
||||
for _, m in ipairs(matches) do
|
||||
local s = 0
|
||||
for i = math.max(idx, m.input_match_start), m.input_match_end do
|
||||
s = s + 1
|
||||
idx = i
|
||||
end
|
||||
idx = idx + 1
|
||||
if s > 0 then
|
||||
s = s * (1 + m.strict_ratio)
|
||||
s = s * (1 + math.max(0, matcher.WORD_BOUNDALY_ORDER_FACTOR - (m.index - offset)) / matcher.WORD_BOUNDALY_ORDER_FACTOR)
|
||||
score = score + s
|
||||
end
|
||||
end
|
||||
|
||||
-- Check remaining input as fuzzy
|
||||
if matches[#matches].input_match_end < #input then
|
||||
if prefix and matcher.fuzzy(input, word, matches) then
|
||||
return score, matches
|
||||
end
|
||||
return 0, {}
|
||||
end
|
||||
|
||||
return score + matcher.NOT_FUZZY_FACTOR, matches
|
||||
end
|
||||
|
||||
--- fuzzy
|
||||
matcher.fuzzy = function(input, word, matches)
|
||||
local last_match = matches[#matches]
|
||||
|
||||
-- Lately specified middle of text.
|
||||
local input_index = last_match.input_match_end + 1
|
||||
for i = 1, #matches - 1 do
|
||||
local curr_match = matches[i]
|
||||
local next_match = matches[i + 1]
|
||||
local word_offset = 0
|
||||
local word_index = char.get_next_semantic_index(word, curr_match.word_match_end)
|
||||
while word_offset + word_index < next_match.word_match_start and input_index <= #input do
|
||||
if char.match(string.byte(word, word_index + word_offset), string.byte(input, input_index)) then
|
||||
input_index = input_index + 1
|
||||
word_offset = word_offset + 1
|
||||
else
|
||||
word_index = char.get_next_semantic_index(word, word_index + word_offset)
|
||||
word_offset = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Remaining text fuzzy match.
|
||||
local last_input_index = input_index
|
||||
local matched = false
|
||||
local word_offset = 0
|
||||
local word_index = last_match.word_match_end + 1
|
||||
local input_match_start = -1
|
||||
local input_match_end = -1
|
||||
local word_match_start = -1
|
||||
local strict_count = 0
|
||||
local match_count = 0
|
||||
while word_offset + word_index <= #word and input_index <= #input do
|
||||
local c1, c2 = string.byte(word, word_index + word_offset), string.byte(input, input_index)
|
||||
if char.match(c1, c2) then
|
||||
if not matched then
|
||||
input_match_start = input_index
|
||||
word_match_start = word_index + word_offset
|
||||
end
|
||||
matched = true
|
||||
input_index = input_index + 1
|
||||
strict_count = strict_count + (c1 == c2 and 1 or 0)
|
||||
match_count = match_count + 1
|
||||
elseif matched then
|
||||
input_index = last_input_index
|
||||
input_match_end = input_index - 1
|
||||
end
|
||||
word_offset = word_offset + 1
|
||||
end
|
||||
if input_index > #input then
|
||||
table.insert(matches, {
|
||||
input_match_start = input_match_start,
|
||||
input_match_end = input_match_end,
|
||||
word_match_start = word_match_start,
|
||||
word_match_end = word_index + word_offset - 1,
|
||||
strict_ratio = strict_count / match_count,
|
||||
fuzzy = true,
|
||||
})
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- find_match_region
|
||||
matcher.find_match_region = function(input, input_start_index, input_end_index, word, word_index)
|
||||
-- determine input position ( woroff -> word_offset )
|
||||
while input_start_index < input_end_index do
|
||||
if char.match(string.byte(input, input_end_index), string.byte(word, word_index)) then
|
||||
break
|
||||
end
|
||||
input_end_index = input_end_index - 1
|
||||
end
|
||||
|
||||
-- Can't determine input position
|
||||
if input_end_index < input_start_index then
|
||||
return nil
|
||||
end
|
||||
|
||||
local input_match_start = -1
|
||||
local input_index = input_end_index
|
||||
local word_offset = 0
|
||||
local strict_count = 0
|
||||
local match_count = 0
|
||||
while input_index <= #input and word_index + word_offset <= #word do
|
||||
local c1 = string.byte(input, input_index)
|
||||
local c2 = string.byte(word, word_index + word_offset)
|
||||
if char.match(c1, c2) then
|
||||
-- Match start.
|
||||
if input_match_start == -1 then
|
||||
input_match_start = input_index
|
||||
end
|
||||
|
||||
strict_count = strict_count + (c1 == c2 and 1 or 0)
|
||||
match_count = match_count + 1
|
||||
word_offset = word_offset + 1
|
||||
else
|
||||
-- Match end (partial region)
|
||||
if input_match_start ~= -1 then
|
||||
return {
|
||||
input_match_start = input_match_start,
|
||||
input_match_end = input_index - 1,
|
||||
word_match_start = word_index,
|
||||
word_match_end = word_index + word_offset - 1,
|
||||
strict_ratio = strict_count / match_count,
|
||||
fuzzy = false,
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
input_index = input_index + 1
|
||||
end
|
||||
|
||||
-- Match end (whole region)
|
||||
if input_match_start ~= -1 then
|
||||
return {
|
||||
input_match_start = input_match_start,
|
||||
input_match_end = input_index - 1,
|
||||
word_match_start = word_index,
|
||||
word_match_end = word_index + word_offset - 1,
|
||||
strict_ratio = strict_count / match_count,
|
||||
fuzzy = false,
|
||||
}
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
return matcher
|
44
bundle/nvim-cmp/lua/cmp/matcher_spec.lua
Normal file
44
bundle/nvim-cmp/lua/cmp/matcher_spec.lua
Normal file
@ -0,0 +1,44 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
|
||||
local matcher = require('cmp.matcher')
|
||||
|
||||
describe('matcher', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('match', function()
|
||||
assert.is.truthy(matcher.match('', 'a') >= 1)
|
||||
assert.is.truthy(matcher.match('a', 'a') >= 1)
|
||||
assert.is.truthy(matcher.match('ab', 'a') == 0)
|
||||
assert.is.truthy(matcher.match('ab', 'ab') > matcher.match('ab', 'a_b'))
|
||||
assert.is.truthy(matcher.match('ab', 'a_b_c') > matcher.match('ac', 'a_b_c'))
|
||||
|
||||
assert.is.truthy(matcher.match('bora', 'border-radius') >= 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('Buffer', 'Buffer') > matcher.match('Buffer', 'buffer'))
|
||||
assert.is.truthy(matcher.match('fmodify', 'fnamemodify') >= 1)
|
||||
assert.is.truthy(matcher.match('candlesingle', 'candle#accept#single') >= 1)
|
||||
assert.is.truthy(matcher.match('conso', 'console') > matcher.match('conso', 'ConstantSourceNode'))
|
||||
assert.is.truthy(matcher.match('var_', 'var_dump') >= 1)
|
||||
assert.is.truthy(matcher.match('my_', 'my_awesome_variable') > matcher.match('my_', 'completion_matching_strategy_list'))
|
||||
assert.is.truthy(matcher.match('luacon', 'lua_context') > matcher.match('luacon', 'LuaContext'))
|
||||
assert.is.truthy(matcher.match('call', 'calc') == 0)
|
||||
|
||||
assert.is.truthy(matcher.match('vi', 'void#') >= 1)
|
||||
assert.is.truthy(matcher.match('vo', 'void#') >= 1)
|
||||
assert.is.truthy(matcher.match('usela', 'useLayoutEffect') > matcher.match('usela', 'useDataLayer'))
|
||||
assert.is.truthy(matcher.match('true', 'v:true', { 'true' }) == matcher.match('true', 'true'))
|
||||
assert.is.truthy(matcher.match('g', 'get', { 'get' }) > matcher.match('g', 'dein#get', { 'dein#get' }))
|
||||
assert.is.truthy(matcher.match('2', '[[2021') >= 1)
|
||||
end)
|
||||
|
||||
it('debug', function()
|
||||
matcher.debug = function(...)
|
||||
print(vim.inspect({ ... }))
|
||||
end
|
||||
-- print(vim.inspect({
|
||||
-- a = matcher.match('true', 'v:true', { 'true' }),
|
||||
-- b = matcher.match('true', 'true'),
|
||||
-- }))
|
||||
end)
|
||||
end)
|
368
bundle/nvim-cmp/lua/cmp/source.lua
Normal file
368
bundle/nvim-cmp/lua/cmp/source.lua
Normal file
@ -0,0 +1,368 @@
|
||||
local context = require('cmp.context')
|
||||
local config = require('cmp.config')
|
||||
local entry = require('cmp.entry')
|
||||
local debug = require('cmp.utils.debug')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local cache = require('cmp.utils.cache')
|
||||
local types = require('cmp.types')
|
||||
local async = require('cmp.utils.async')
|
||||
local pattern = require('cmp.utils.pattern')
|
||||
local char = require('cmp.utils.char')
|
||||
|
||||
---@class cmp.Source
|
||||
---@field public id number
|
||||
---@field public name string
|
||||
---@field public source any
|
||||
---@field public cache cmp.Cache
|
||||
---@field public revision number
|
||||
---@field public context cmp.Context
|
||||
---@field public incomplete boolean
|
||||
---@field public is_triggered_by_symbol boolean
|
||||
---@field public entries cmp.Entry[]
|
||||
---@field public offset number
|
||||
---@field public request_offset number
|
||||
---@field public status cmp.SourceStatus
|
||||
---@field public complete_dedup function
|
||||
local source = {}
|
||||
|
||||
---@alias cmp.SourceStatus "1" | "2" | "3"
|
||||
source.SourceStatus = {}
|
||||
source.SourceStatus.WAITING = 1
|
||||
source.SourceStatus.FETCHING = 2
|
||||
source.SourceStatus.COMPLETED = 3
|
||||
|
||||
---@return cmp.Source
|
||||
source.new = function(name, s)
|
||||
local self = setmetatable({}, { __index = source })
|
||||
self.id = misc.id('cmp.source.new')
|
||||
self.name = name
|
||||
self.source = s
|
||||
self.cache = cache.new()
|
||||
self.complete_dedup = async.dedup()
|
||||
self.revision = 0
|
||||
self:reset()
|
||||
return self
|
||||
end
|
||||
|
||||
---Reset current completion state
|
||||
---@return boolean
|
||||
source.reset = function(self)
|
||||
self.cache:clear()
|
||||
self.revision = self.revision + 1
|
||||
self.context = context.empty()
|
||||
self.request_offset = -1
|
||||
self.is_triggered_by_symbol = false
|
||||
self.incomplete = false
|
||||
self.entries = {}
|
||||
self.offset = -1
|
||||
self.status = source.SourceStatus.WAITING
|
||||
self.complete_dedup(function() end)
|
||||
end
|
||||
|
||||
---Return source option
|
||||
---@return cmp.SourceConfig
|
||||
source.get_config = function(self)
|
||||
return config.get_source_config(self.name) or {}
|
||||
end
|
||||
|
||||
---Get fetching time
|
||||
source.get_fetching_time = function(self)
|
||||
if self.status == source.SourceStatus.FETCHING then
|
||||
return vim.loop.now() - self.context.time
|
||||
end
|
||||
return 100 * 1000 -- return pseudo time if source isn't fetching.
|
||||
end
|
||||
|
||||
---Return filtered entries
|
||||
---@param ctx cmp.Context
|
||||
---@return cmp.Entry[]
|
||||
source.get_entries = function(self, ctx)
|
||||
if self.offset == -1 then
|
||||
return {}
|
||||
end
|
||||
|
||||
local target_entries = (function()
|
||||
local key = { 'get_entries', self.revision }
|
||||
for i = ctx.cursor.col, self.offset, -1 do
|
||||
key[3] = string.sub(ctx.cursor_before_line, 1, i)
|
||||
local prev_entries = self.cache:get(key)
|
||||
if prev_entries then
|
||||
return prev_entries
|
||||
end
|
||||
end
|
||||
return self.entries
|
||||
end)()
|
||||
|
||||
local inputs = {}
|
||||
local entries = {}
|
||||
for _, e in ipairs(target_entries) do
|
||||
local o = e:get_offset()
|
||||
if not inputs[o] then
|
||||
inputs[o] = string.sub(ctx.cursor_before_line, o)
|
||||
end
|
||||
|
||||
local match = e:match(inputs[o])
|
||||
e.score = match.score
|
||||
e.exact = false
|
||||
if e.score >= 1 then
|
||||
e.matches = match.matches
|
||||
e.exact = e:get_filter_text() == inputs[o] or e:get_word() == inputs[o]
|
||||
table.insert(entries, e)
|
||||
end
|
||||
end
|
||||
self.cache:set({ 'get_entries', self.revision, ctx.cursor_before_line }, entries)
|
||||
|
||||
local max_item_count = self:get_config().max_item_count or 200
|
||||
local limited_entries = {}
|
||||
for _, e in ipairs(entries) do
|
||||
table.insert(limited_entries, e)
|
||||
if max_item_count and #limited_entries >= max_item_count then
|
||||
break
|
||||
end
|
||||
end
|
||||
return limited_entries
|
||||
end
|
||||
|
||||
---Get default insert range
|
||||
---@return lsp.Range|nil
|
||||
source.get_default_insert_range = function(self)
|
||||
if not self.context then
|
||||
return nil
|
||||
end
|
||||
|
||||
return self.cache:ensure({ 'get_default_insert_range', self.revision }, function()
|
||||
return {
|
||||
start = {
|
||||
line = self.context.cursor.row - 1,
|
||||
character = misc.to_utfindex(self.context.cursor_line, self.offset),
|
||||
},
|
||||
['end'] = {
|
||||
line = self.context.cursor.row - 1,
|
||||
character = misc.to_utfindex(self.context.cursor_line, self.context.cursor.col),
|
||||
},
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
---Get default replace range
|
||||
---@return lsp.Range|nil
|
||||
source.get_default_replace_range = function(self)
|
||||
if not self.context then
|
||||
return nil
|
||||
end
|
||||
|
||||
return self.cache:ensure({ 'get_default_replace_range', self.revision }, function()
|
||||
local _, e = pattern.offset('^' .. '\\%(' .. self:get_keyword_pattern() .. '\\)', string.sub(self.context.cursor_line, self.offset))
|
||||
return {
|
||||
start = {
|
||||
line = self.context.cursor.row - 1,
|
||||
character = misc.to_utfindex(self.context.cursor_line, self.offset),
|
||||
},
|
||||
['end'] = {
|
||||
line = self.context.cursor.row - 1,
|
||||
character = misc.to_utfindex(self.context.cursor_line, e and self.offset + e - 1 or self.context.cursor.col),
|
||||
},
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
---Return source name.
|
||||
source.get_debug_name = function(self)
|
||||
local name = self.name
|
||||
if self.source.get_debug_name then
|
||||
name = self.source:get_debug_name()
|
||||
end
|
||||
return name
|
||||
end
|
||||
|
||||
---Return the source is available or not.
|
||||
source.is_available = function(self)
|
||||
if self.source.is_available then
|
||||
return self.source:is_available()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---Get keyword_pattern
|
||||
---@return string
|
||||
source.get_keyword_pattern = function(self)
|
||||
local c = self:get_config()
|
||||
if c.keyword_pattern then
|
||||
return c.keyword_pattern
|
||||
end
|
||||
if self.source.get_keyword_pattern then
|
||||
return self.source:get_keyword_pattern({
|
||||
option = self:get_config().opts,
|
||||
})
|
||||
end
|
||||
return config.get().completion.keyword_pattern
|
||||
end
|
||||
|
||||
---Get keyword_length
|
||||
---@return number
|
||||
source.get_keyword_length = function(self)
|
||||
local c = self:get_config()
|
||||
if c.keyword_length then
|
||||
return c.keyword_length
|
||||
end
|
||||
return config.get().completion.keyword_length or 1
|
||||
end
|
||||
|
||||
---Get trigger_characters
|
||||
---@return string[]
|
||||
source.get_trigger_characters = function(self)
|
||||
local trigger_characters = {}
|
||||
if self.source.get_trigger_characters then
|
||||
trigger_characters = self.source:get_trigger_characters({
|
||||
option = self:get_config().opts,
|
||||
}) or {}
|
||||
end
|
||||
if config.get().completion.get_trigger_characters then
|
||||
return config.get().completion.get_trigger_characters(trigger_characters)
|
||||
end
|
||||
return trigger_characters
|
||||
end
|
||||
|
||||
---Invoke completion
|
||||
---@param ctx cmp.Context
|
||||
---@param callback function
|
||||
---@return boolean Return true if not trigger completion.
|
||||
source.complete = function(self, ctx, callback)
|
||||
local offset = ctx:get_offset(self:get_keyword_pattern())
|
||||
if ctx.cursor.col <= offset then
|
||||
self:reset()
|
||||
end
|
||||
|
||||
local before_char = string.sub(ctx.cursor_before_line, -1)
|
||||
local before_char_iw = string.match(ctx.cursor_before_line, '(.)%s*$') or before_char
|
||||
|
||||
if ctx:get_reason() == types.cmp.ContextReason.TriggerOnly then
|
||||
if string.match(before_char, '^%a+$') then
|
||||
before_char = ''
|
||||
end
|
||||
if string.match(before_char_iw, '^%a+$') then
|
||||
before_char_iw = ''
|
||||
end
|
||||
end
|
||||
|
||||
local completion_context
|
||||
if ctx:get_reason() == types.cmp.ContextReason.Manual then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
||||
}
|
||||
else
|
||||
if vim.tbl_contains(self:get_trigger_characters(), before_char) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
||||
triggerCharacter = before_char,
|
||||
}
|
||||
elseif vim.tbl_contains(self:get_trigger_characters(), before_char_iw) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerCharacter,
|
||||
triggerCharacter = before_char_iw,
|
||||
}
|
||||
elseif ctx:get_reason() ~= types.cmp.ContextReason.TriggerOnly then
|
||||
if self:get_keyword_length() <= (ctx.cursor.col - offset) then
|
||||
if self.incomplete and self.context.cursor.col ~= ctx.cursor.col then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.TriggerForIncompleteCompletions,
|
||||
}
|
||||
elseif not vim.tbl_contains({ self.request_offset, self.offset }, offset) then
|
||||
completion_context = {
|
||||
triggerKind = types.lsp.CompletionTriggerKind.Invoked,
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
self:reset()
|
||||
end
|
||||
end
|
||||
|
||||
if not completion_context then
|
||||
if ctx:get_reason() == types.cmp.ContextReason.TriggerOnly then
|
||||
self:reset()
|
||||
end
|
||||
debug.log(self:get_debug_name(), 'skip completion')
|
||||
return
|
||||
end
|
||||
|
||||
if completion_context.triggerKind == types.lsp.CompletionTriggerKind.TriggerCharacter then
|
||||
self.is_triggered_by_symbol = char.is_symbol(string.byte(completion_context.triggerCharacter))
|
||||
end
|
||||
|
||||
debug.log(self:get_debug_name(), 'request', offset, vim.inspect(completion_context))
|
||||
local prev_status = self.status
|
||||
self.status = source.SourceStatus.FETCHING
|
||||
self.request_offset = offset
|
||||
self.offset = offset
|
||||
self.context = ctx
|
||||
self.source:complete(
|
||||
{
|
||||
context = ctx,
|
||||
offset = self.offset,
|
||||
option = self:get_config().opts,
|
||||
completion_context = completion_context,
|
||||
},
|
||||
self.complete_dedup(vim.schedule_wrap(function(response)
|
||||
response = response or {}
|
||||
|
||||
self.incomplete = response.isIncomplete or false
|
||||
|
||||
if #(response.items or response) > 0 then
|
||||
debug.log(self:get_debug_name(), 'retrieve', #(response.items or response))
|
||||
local old_offset = self.offset
|
||||
local old_entries = self.entries
|
||||
|
||||
self.status = source.SourceStatus.COMPLETED
|
||||
self.entries = {}
|
||||
for i, item in ipairs(response.items or response) do
|
||||
if (misc.safe(item) or {}).label then
|
||||
local e = entry.new(ctx, self, item)
|
||||
self.entries[i] = e
|
||||
self.offset = math.min(self.offset, e:get_offset())
|
||||
end
|
||||
end
|
||||
self.revision = self.revision + 1
|
||||
if #self:get_entries(ctx) == 0 then
|
||||
self.offset = old_offset
|
||||
self.entries = old_entries
|
||||
self.revision = self.revision + 1
|
||||
end
|
||||
else
|
||||
debug.log(self:get_debug_name(), 'continue', 'nil')
|
||||
if completion_context.triggerKind == types.lsp.CompletionTriggerKind.TriggerCharacter then
|
||||
self:reset()
|
||||
end
|
||||
self.status = prev_status
|
||||
end
|
||||
callback()
|
||||
end))
|
||||
)
|
||||
return true
|
||||
end
|
||||
|
||||
---Resolve CompletionItem
|
||||
---@param item lsp.CompletionItem
|
||||
---@param callback fun(item: lsp.CompletionItem)
|
||||
source.resolve = function(self, item, callback)
|
||||
if not self.source.resolve then
|
||||
return callback(item)
|
||||
end
|
||||
self.source:resolve(item, function(resolved_item)
|
||||
callback(resolved_item or item)
|
||||
end)
|
||||
end
|
||||
|
||||
---Execute command
|
||||
---@param item lsp.CompletionItem
|
||||
---@param callback fun()
|
||||
source.execute = function(self, item, callback)
|
||||
if not self.source.execute then
|
||||
return callback()
|
||||
end
|
||||
self.source:execute(item, function()
|
||||
callback()
|
||||
end)
|
||||
end
|
||||
|
||||
return source
|
109
bundle/nvim-cmp/lua/cmp/source_spec.lua
Normal file
109
bundle/nvim-cmp/lua/cmp/source_spec.lua
Normal file
@ -0,0 +1,109 @@
|
||||
local config = require('cmp.config')
|
||||
local spec = require('cmp.utils.spec')
|
||||
|
||||
local source = require('cmp.source')
|
||||
|
||||
describe('source', function()
|
||||
before_each(spec.before)
|
||||
|
||||
describe('keyword length', function()
|
||||
it('not enough', function()
|
||||
config.set_buffer({
|
||||
completion = {
|
||||
keyword_length = 3,
|
||||
},
|
||||
}, vim.api.nvim_get_current_buf())
|
||||
|
||||
local state = spec.state('', 1, 1)
|
||||
local s = source.new('spec', {
|
||||
complete = function(_, _, callback)
|
||||
callback({ { label = 'spec' } })
|
||||
end,
|
||||
})
|
||||
assert.is.truthy(not s:complete(state.input('a'), function() end))
|
||||
end)
|
||||
|
||||
it('enough', function()
|
||||
config.set_buffer({
|
||||
completion = {
|
||||
keyword_length = 3,
|
||||
},
|
||||
}, vim.api.nvim_get_current_buf())
|
||||
|
||||
local state = spec.state('', 1, 1)
|
||||
local s = source.new('spec', {
|
||||
complete = function(_, _, callback)
|
||||
callback({ { label = 'spec' } })
|
||||
end,
|
||||
})
|
||||
assert.is.truthy(s:complete(state.input('aiu'), function() end))
|
||||
end)
|
||||
|
||||
it('enough -> not enough', function()
|
||||
config.set_buffer({
|
||||
completion = {
|
||||
keyword_length = 3,
|
||||
},
|
||||
}, vim.api.nvim_get_current_buf())
|
||||
|
||||
local state = spec.state('', 1, 1)
|
||||
local s = source.new('spec', {
|
||||
complete = function(_, _, callback)
|
||||
callback({ { label = 'spec' } })
|
||||
end,
|
||||
})
|
||||
assert.is.truthy(s:complete(state.input('aiu'), function() end))
|
||||
assert.is.truthy(not s:complete(state.backspace(), function() end))
|
||||
end)
|
||||
|
||||
it('continue', function()
|
||||
config.set_buffer({
|
||||
completion = {
|
||||
keyword_length = 3,
|
||||
},
|
||||
}, vim.api.nvim_get_current_buf())
|
||||
|
||||
local state = spec.state('', 1, 1)
|
||||
local s = source.new('spec', {
|
||||
complete = function(_, _, callback)
|
||||
callback({ { label = 'spec' } })
|
||||
end,
|
||||
})
|
||||
assert.is.truthy(s:complete(state.input('aiu'), function() end))
|
||||
assert.is.truthy(not s:complete(state.input('eo'), function() end))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('isIncomplete', function()
|
||||
it('isIncomplete=true', function()
|
||||
local state = spec.state('', 1, 1)
|
||||
local s = source.new('spec', {
|
||||
complete = function(_, _, callback)
|
||||
callback({
|
||||
items = { { label = 'spec' } },
|
||||
isIncomplete = true,
|
||||
})
|
||||
end,
|
||||
})
|
||||
vim.wait(100, function()
|
||||
return s.status == source.SourceStatus.COMPLETED
|
||||
end, 100, false)
|
||||
assert.is.truthy(s:complete(state.input('s'), function() end))
|
||||
vim.wait(100, function()
|
||||
return s.status == source.SourceStatus.COMPLETED
|
||||
end, 100, false)
|
||||
assert.is.truthy(s:complete(state.input('p'), function() end))
|
||||
vim.wait(100, function()
|
||||
return s.status == source.SourceStatus.COMPLETED
|
||||
end, 100, false)
|
||||
assert.is.truthy(s:complete(state.input('e'), function() end))
|
||||
vim.wait(100, function()
|
||||
return s.status == source.SourceStatus.COMPLETED
|
||||
end, 100, false)
|
||||
assert.is.truthy(s:complete(state.input('c'), function() end))
|
||||
vim.wait(100, function()
|
||||
return s.status == source.SourceStatus.COMPLETED
|
||||
end, 100, false)
|
||||
end)
|
||||
end)
|
||||
end)
|
128
bundle/nvim-cmp/lua/cmp/types/cmp.lua
Normal file
128
bundle/nvim-cmp/lua/cmp/types/cmp.lua
Normal file
@ -0,0 +1,128 @@
|
||||
local cmp = {}
|
||||
|
||||
---@alias cmp.ConfirmBehavior "'insert'" | "'replace'"
|
||||
cmp.ConfirmBehavior = {}
|
||||
cmp.ConfirmBehavior.Insert = 'insert'
|
||||
cmp.ConfirmBehavior.Replace = 'replace'
|
||||
|
||||
---@alias cmp.SelectBehavior "'insert'" | "'select'"
|
||||
cmp.SelectBehavior = {}
|
||||
cmp.SelectBehavior.Insert = 'insert'
|
||||
cmp.SelectBehavior.Select = 'select'
|
||||
|
||||
---@alias cmp.ContextReason "'auto'" | "'manual'" | "'none'"
|
||||
cmp.ContextReason = {}
|
||||
cmp.ContextReason.Auto = 'auto'
|
||||
cmp.ContextReason.Manual = 'manual'
|
||||
cmp.ContextReason.TriggerOnly = 'triggerOnly'
|
||||
cmp.ContextReason.None = 'none'
|
||||
|
||||
---@alias cmp.TriggerEvent "'InsertEnter'" | "'TextChanged'"
|
||||
cmp.TriggerEvent = {}
|
||||
cmp.TriggerEvent.InsertEnter = 'InsertEnter'
|
||||
cmp.TriggerEvent.TextChanged = 'TextChanged'
|
||||
|
||||
---@alias cmp.PreselectMode "'item'" | "'None'"
|
||||
cmp.PreselectMode = {}
|
||||
cmp.PreselectMode.Item = 'item'
|
||||
cmp.PreselectMode.None = 'none'
|
||||
|
||||
---@alias cmp.ItemField "'abbr'" | "'kind'" | "'menu'"
|
||||
cmp.ItemField = {}
|
||||
cmp.ItemField.Abbr = 'abbr'
|
||||
cmp.ItemField.Kind = 'kind'
|
||||
cmp.ItemField.Menu = 'menu'
|
||||
|
||||
---@class cmp.ContextOption
|
||||
---@field public reason cmp.ContextReason|nil
|
||||
|
||||
---@class cmp.ConfirmOption
|
||||
---@field public behavior cmp.ConfirmBehavior
|
||||
|
||||
---@class cmp.SelectOption
|
||||
---@field public behavior cmp.SelectBehavior
|
||||
|
||||
---@class cmp.SnippetExpansionParams
|
||||
---@field public body string
|
||||
---@field public insert_text_mode number
|
||||
|
||||
---@class cmp.Setup
|
||||
---@field public __call fun(c: cmp.ConfigSchema)
|
||||
---@field public buffer fun(c: cmp.ConfigSchema)
|
||||
---@field public global fun(c: cmp.ConfigSchema)
|
||||
---@field public cmdline fun(type: string, c: cmp.ConfigSchema)
|
||||
|
||||
---@class cmp.SourceBaseApiParams
|
||||
---@field public option table
|
||||
|
||||
---@class cmp.SourceCompletionApiParams : cmp.SourceBaseApiParams
|
||||
---@field public context cmp.Context
|
||||
---@field public offset number
|
||||
---@field public completion_context lsp.CompletionContext
|
||||
|
||||
---@class cmp.Mapping
|
||||
---@field public i nil|function(fallback: function): void
|
||||
---@field public c nil|function(fallback: function): void
|
||||
---@field public x nil|function(fallback: function): void
|
||||
---@field public s nil|function(fallback: function): void
|
||||
|
||||
---@class cmp.ConfigSchema
|
||||
---@field private revision number
|
||||
---@field public enabled fun():boolean|boolean
|
||||
---@field public preselect cmp.PreselectMode
|
||||
---@field public completion cmp.CompletionConfig
|
||||
---@field public documentation cmp.DocumentationConfig|"false"
|
||||
---@field public confirmation cmp.ConfirmationConfig
|
||||
---@field public sorting cmp.SortingConfig
|
||||
---@field public formatting cmp.FormattingConfig
|
||||
---@field public snippet cmp.SnippetConfig
|
||||
---@field public mapping table<string, cmp.Mapping>
|
||||
---@field public sources cmp.SourceConfig[]
|
||||
---@field public experimental cmp.ExperimentalConfig
|
||||
|
||||
---@class cmp.CompletionConfig
|
||||
---@field public autocomplete cmp.TriggerEvent[]
|
||||
---@field public completeopt string
|
||||
---@field public keyword_pattern string
|
||||
---@field public keyword_length number
|
||||
---@field public get_trigger_characters fun(trigger_characters: string[]): string[]
|
||||
|
||||
---@class cmp.DocumentationConfig
|
||||
---@field public border string[]
|
||||
---@field public winhighlight string
|
||||
---@field public maxwidth number|nil
|
||||
---@field public maxheight number|nil
|
||||
---@field public zindex number|nil
|
||||
|
||||
---@class cmp.ConfirmationConfig
|
||||
---@field public default_behavior cmp.ConfirmBehavior
|
||||
---@field public get_commit_characters fun(commit_characters: string[]): string[]
|
||||
|
||||
---@class cmp.SortingConfig
|
||||
---@field public priority_weight number
|
||||
---@field public comparators function[]
|
||||
|
||||
---@class cmp.FormattingConfig
|
||||
---@field public fields cmp.ItemField[]
|
||||
---@field public format fun(entry: cmp.Entry, vim_item: vim.CompletedItem): vim.CompletedItem
|
||||
|
||||
---@class cmp.SnippetConfig
|
||||
---@field public expand fun(args: cmp.SnippetExpansionParams)
|
||||
|
||||
---@class cmp.ExperimentalConfig
|
||||
---@field public native_menu boolean
|
||||
---@field public ghost_text cmp.GhostTextConfig|"false"
|
||||
|
||||
---@class cmp.GhostTextConfig
|
||||
---@field hl_group string
|
||||
|
||||
---@class cmp.SourceConfig
|
||||
---@field public name string
|
||||
---@field public opts table
|
||||
---@field public priority number|nil
|
||||
---@field public keyword_pattern string
|
||||
---@field public keyword_length number
|
||||
---@field public max_item_count number
|
||||
---@field public group_index number
|
||||
|
||||
return cmp
|
7
bundle/nvim-cmp/lua/cmp/types/init.lua
Normal file
7
bundle/nvim-cmp/lua/cmp/types/init.lua
Normal file
@ -0,0 +1,7 @@
|
||||
local types = {}
|
||||
|
||||
types.cmp = require('cmp.types.cmp')
|
||||
types.lsp = require('cmp.types.lsp')
|
||||
types.vim = require('cmp.types.vim')
|
||||
|
||||
return types
|
197
bundle/nvim-cmp/lua/cmp/types/lsp.lua
Normal file
197
bundle/nvim-cmp/lua/cmp/types/lsp.lua
Normal file
@ -0,0 +1,197 @@
|
||||
local misc = require('cmp.utils.misc')
|
||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/
|
||||
---@class lsp
|
||||
local lsp = {}
|
||||
|
||||
lsp.Position = {}
|
||||
|
||||
---Convert lsp.Position to vim.Position
|
||||
---@param buf number|string
|
||||
---@param position lsp.Position
|
||||
---@return vim.Position
|
||||
lsp.Position.to_vim = function(buf, position)
|
||||
if not vim.api.nvim_buf_is_loaded(buf) then
|
||||
vim.fn.bufload(buf)
|
||||
end
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, position.line, position.line + 1, false)
|
||||
if #lines > 0 then
|
||||
return {
|
||||
row = position.line + 1,
|
||||
col = misc.to_vimindex(lines[1], position.character),
|
||||
}
|
||||
end
|
||||
return {
|
||||
row = position.line + 1,
|
||||
col = position.character + 1,
|
||||
}
|
||||
end
|
||||
|
||||
---Convert lsp.Position to vim.Position
|
||||
---@param buf number|string
|
||||
---@param position vim.Position
|
||||
---@return lsp.Position
|
||||
lsp.Position.to_lsp = function(buf, position)
|
||||
if not vim.api.nvim_buf_is_loaded(buf) then
|
||||
vim.fn.bufload(buf)
|
||||
end
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, position.row - 1, position.row, false)
|
||||
if #lines > 0 then
|
||||
return {
|
||||
line = position.row - 1,
|
||||
character = misc.to_utfindex(lines[1], position.col),
|
||||
}
|
||||
end
|
||||
return {
|
||||
line = position.row - 1,
|
||||
character = position.col - 1,
|
||||
}
|
||||
end
|
||||
|
||||
lsp.Range = {}
|
||||
|
||||
---Convert lsp.Position to vim.Position
|
||||
---@param buf number|string
|
||||
---@param range lsp.Range
|
||||
---@return vim.Range
|
||||
lsp.Range.to_vim = function(buf, range)
|
||||
return {
|
||||
start = lsp.Position.to_vim(buf, range.start),
|
||||
['end'] = lsp.Position.to_vim(buf, range['end']),
|
||||
}
|
||||
end
|
||||
|
||||
---Convert lsp.Position to vim.Position
|
||||
---@param buf number|string
|
||||
---@param range vim.Range
|
||||
---@return lsp.Range
|
||||
lsp.Range.to_lsp = function(buf, range)
|
||||
return {
|
||||
start = lsp.Position.to_lsp(buf, range.start),
|
||||
['end'] = lsp.Position.to_lsp(buf, range['end']),
|
||||
}
|
||||
end
|
||||
|
||||
---@alias lsp.CompletionTriggerKind "1" | "2" | "3"
|
||||
lsp.CompletionTriggerKind = {}
|
||||
lsp.CompletionTriggerKind.Invoked = 1
|
||||
lsp.CompletionTriggerKind.TriggerCharacter = 2
|
||||
lsp.CompletionTriggerKind.TriggerForIncompleteCompletions = 3
|
||||
|
||||
---@class lsp.CompletionContext
|
||||
---@field public triggerKind lsp.CompletionTriggerKind
|
||||
---@field public triggerCharacter string|nil
|
||||
|
||||
---@alias lsp.InsertTextFormat "1" | "2"
|
||||
lsp.InsertTextFormat = {}
|
||||
lsp.InsertTextFormat.PlainText = 1
|
||||
lsp.InsertTextFormat.Snippet = 2
|
||||
lsp.InsertTextFormat = vim.tbl_add_reverse_lookup(lsp.InsertTextFormat)
|
||||
|
||||
---@alias lsp.InsertTextMode "1" | "2"
|
||||
lsp.InsertTextMode = {}
|
||||
lsp.InsertTextMode.AsIs = 0
|
||||
lsp.InsertTextMode.AdjustIndentation = 1
|
||||
lsp.InsertTextMode = vim.tbl_add_reverse_lookup(lsp.InsertTextMode)
|
||||
|
||||
---@alias lsp.MarkupKind "'plaintext'" | "'markdown'"
|
||||
lsp.MarkupKind = {}
|
||||
lsp.MarkupKind.PlainText = 'plaintext'
|
||||
lsp.MarkupKind.Markdown = 'markdown'
|
||||
lsp.MarkupKind.Markdown = 'markdown'
|
||||
lsp.MarkupKind = vim.tbl_add_reverse_lookup(lsp.MarkupKind)
|
||||
|
||||
---@alias lsp.CompletionItemTag "1"
|
||||
lsp.CompletionItemTag = {}
|
||||
lsp.CompletionItemTag.Deprecated = 1
|
||||
lsp.CompletionItemTag = vim.tbl_add_reverse_lookup(lsp.CompletionItemTag)
|
||||
|
||||
---@alias lsp.CompletionItemKind "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "20" | "21" | "22" | "23" | "24" | "25"
|
||||
lsp.CompletionItemKind = {}
|
||||
lsp.CompletionItemKind.Text = 1
|
||||
lsp.CompletionItemKind.Method = 2
|
||||
lsp.CompletionItemKind.Function = 3
|
||||
lsp.CompletionItemKind.Constructor = 4
|
||||
lsp.CompletionItemKind.Field = 5
|
||||
lsp.CompletionItemKind.Variable = 6
|
||||
lsp.CompletionItemKind.Class = 7
|
||||
lsp.CompletionItemKind.Interface = 8
|
||||
lsp.CompletionItemKind.Module = 9
|
||||
lsp.CompletionItemKind.Property = 10
|
||||
lsp.CompletionItemKind.Unit = 11
|
||||
lsp.CompletionItemKind.Value = 12
|
||||
lsp.CompletionItemKind.Enum = 13
|
||||
lsp.CompletionItemKind.Keyword = 14
|
||||
lsp.CompletionItemKind.Snippet = 15
|
||||
lsp.CompletionItemKind.Color = 16
|
||||
lsp.CompletionItemKind.File = 17
|
||||
lsp.CompletionItemKind.Reference = 18
|
||||
lsp.CompletionItemKind.Folder = 19
|
||||
lsp.CompletionItemKind.EnumMember = 20
|
||||
lsp.CompletionItemKind.Constant = 21
|
||||
lsp.CompletionItemKind.Struct = 22
|
||||
lsp.CompletionItemKind.Event = 23
|
||||
lsp.CompletionItemKind.Operator = 24
|
||||
lsp.CompletionItemKind.TypeParameter = 25
|
||||
lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind)
|
||||
|
||||
---@class lsp.CompletionList
|
||||
---@field public isIncomplete boolean
|
||||
---@field public items lsp.CompletionItem[]
|
||||
|
||||
---@alias lsp.CompletionResponse lsp.CompletionList|lsp.CompletionItem[]|nil
|
||||
|
||||
---@class lsp.MarkupContent
|
||||
---@field public kind lsp.MarkupKind
|
||||
---@field public value string
|
||||
|
||||
---@class lsp.Position
|
||||
---@field public line number
|
||||
---@field public character number
|
||||
|
||||
---@class lsp.Range
|
||||
---@field public start lsp.Position
|
||||
---@field public end lsp.Position
|
||||
|
||||
---@class lsp.Command
|
||||
---@field public title string
|
||||
---@field public command string
|
||||
---@field public arguments any[]|nil
|
||||
|
||||
---@class lsp.TextEdit
|
||||
---@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
|
||||
---@field public newText string
|
||||
|
||||
---@class lsp.CompletionItemLabelDetails
|
||||
---@field public detail string|nil
|
||||
---@field public description string|nil
|
||||
|
||||
---@class lsp.CompletionItem
|
||||
---@field public label string
|
||||
---@field public labelDetails lsp.CompletionItemLabelDetails|nil
|
||||
---@field public kind lsp.CompletionItemKind|nil
|
||||
---@field public tags lsp.CompletionItemTag[]|nil
|
||||
---@field public detail string|nil
|
||||
---@field public documentation lsp.MarkupContent|string|nil
|
||||
---@field public deprecated boolean|nil
|
||||
---@field public preselect boolean|nil
|
||||
---@field public sortText string|nil
|
||||
---@field public filterText string|nil
|
||||
---@field public insertText string|nil
|
||||
---@field public insertTextFormat lsp.InsertTextFormat
|
||||
---@field public insertTextMode lsp.InsertTextMode
|
||||
---@field public textEdit lsp.TextEdit|lsp.InsertReplaceTextEdit|nil
|
||||
---@field public additionalTextEdits lsp.TextEdit[]
|
||||
---@field public commitCharacters string[]|nil
|
||||
---@field public command lsp.Command|nil
|
||||
---@field public data any|nil
|
||||
---
|
||||
---TODO: Should send the issue for upstream?
|
||||
---@field public word string|nil
|
||||
---@field public dup boolean|nil
|
||||
|
||||
return lsp
|
46
bundle/nvim-cmp/lua/cmp/types/lsp_spec.lua
Normal file
46
bundle/nvim-cmp/lua/cmp/types/lsp_spec.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
local lsp = require('cmp.types.lsp')
|
||||
|
||||
describe('types.lsp', function()
|
||||
before_each(spec.before)
|
||||
describe('Position', function()
|
||||
vim.fn.setline('1', {
|
||||
'あいうえお',
|
||||
'かきくけこ',
|
||||
'さしすせそ',
|
||||
})
|
||||
local vim_position, lsp_position
|
||||
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 3 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 10)
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 3)
|
||||
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 0 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 1)
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 0)
|
||||
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 5 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 16)
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 5)
|
||||
|
||||
-- overflow (lsp -> vim)
|
||||
vim_position = lsp.Position.to_vim('%', { line = 1, character = 6 })
|
||||
assert.are.equal(vim_position.row, 2)
|
||||
assert.are.equal(vim_position.col, 16)
|
||||
|
||||
-- overflow(vim -> lsp)
|
||||
vim_position.col = vim_position.col + 1
|
||||
lsp_position = lsp.Position.to_lsp('%', vim_position)
|
||||
assert.are.equal(lsp_position.line, 1)
|
||||
assert.are.equal(lsp_position.character, 5)
|
||||
end)
|
||||
end)
|
17
bundle/nvim-cmp/lua/cmp/types/vim.lua
Normal file
17
bundle/nvim-cmp/lua/cmp/types/vim.lua
Normal file
@ -0,0 +1,17 @@
|
||||
---@class vim.CompletedItem
|
||||
---@field public word string
|
||||
---@field public abbr string|nil
|
||||
---@field public kind string|nil
|
||||
---@field public menu string|nil
|
||||
---@field public equal "1"|nil
|
||||
---@field public empty "1"|nil
|
||||
---@field public dup "1"|nil
|
||||
---@field public id any
|
||||
|
||||
---@class vim.Position
|
||||
---@field public row number
|
||||
---@field public col number
|
||||
|
||||
---@class vim.Range
|
||||
---@field public start vim.Position
|
||||
---@field public end vim.Position
|
68
bundle/nvim-cmp/lua/cmp/utils/api.lua
Normal file
68
bundle/nvim-cmp/lua/cmp/utils/api.lua
Normal file
@ -0,0 +1,68 @@
|
||||
local api = {}
|
||||
|
||||
local CTRL_V = vim.api.nvim_replace_termcodes('<C-v>', true, true, true)
|
||||
local CTRL_S = vim.api.nvim_replace_termcodes('<C-s>', true, true, true)
|
||||
|
||||
api.get_mode = function()
|
||||
local mode = vim.api.nvim_get_mode().mode:sub(1, 1)
|
||||
if mode == 'i' then
|
||||
return 'i' -- insert
|
||||
elseif mode == 'v' or mode == 'V' or mode == CTRL_V then
|
||||
return 'x' -- visual
|
||||
elseif mode == 's' or mode == 'S' or mode == CTRL_S then
|
||||
return 's' -- select
|
||||
elseif mode == 'c' and vim.fn.getcmdtype() ~= '=' then
|
||||
return 'c' -- cmdline
|
||||
end
|
||||
end
|
||||
|
||||
api.is_insert_mode = function()
|
||||
return api.get_mode() == 'i'
|
||||
end
|
||||
|
||||
api.is_cmdline_mode = function()
|
||||
return api.get_mode() == 'c'
|
||||
end
|
||||
|
||||
api.is_select_mode = function()
|
||||
return api.get_mode() == 's'
|
||||
end
|
||||
|
||||
api.is_visual_mode = function()
|
||||
return api.get_mode() == 'x'
|
||||
end
|
||||
|
||||
api.is_suitable_mode = function()
|
||||
local mode = api.get_mode()
|
||||
return mode == 'i' or mode == 'c'
|
||||
end
|
||||
|
||||
api.get_current_line = function()
|
||||
if api.is_cmdline_mode() then
|
||||
return vim.fn.getcmdline()
|
||||
end
|
||||
return vim.api.nvim_get_current_line()
|
||||
end
|
||||
|
||||
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 }
|
||||
end
|
||||
return vim.api.nvim_win_get_cursor(0)
|
||||
end
|
||||
|
||||
api.get_screen_cursor = function()
|
||||
if api.is_cmdline_mode() then
|
||||
return api.get_cursor()
|
||||
end
|
||||
local cursor = api.get_cursor()
|
||||
local pos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1)
|
||||
return { pos.row, pos.col - 1 }
|
||||
end
|
||||
|
||||
api.get_cursor_before_line = function()
|
||||
local cursor = api.get_cursor()
|
||||
return string.sub(api.get_current_line(), 1, cursor[2])
|
||||
end
|
||||
|
||||
return api
|
46
bundle/nvim-cmp/lua/cmp/utils/api_spec.lua
Normal file
46
bundle/nvim-cmp/lua/cmp/utils/api_spec.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
describe('api', function()
|
||||
describe('get_cursor', function()
|
||||
before_each(spec.before)
|
||||
it('insert-mode', function()
|
||||
local cursor
|
||||
feedkeys.call(keymap.t('i\t1234567890'), 'nx', function()
|
||||
cursor = api.get_cursor()
|
||||
end)
|
||||
assert.are.equal(cursor[2], 11)
|
||||
end)
|
||||
it('cmdline-mode', function()
|
||||
local cursor
|
||||
keymap.set_map(0, 'c', '<Plug>(cmp-spec-spy)', function()
|
||||
cursor = api.get_cursor()
|
||||
end, { expr = true, noremap = true })
|
||||
feedkeys.call(keymap.t(':\t1234567890'), 'n')
|
||||
feedkeys.call(keymap.t('<Plug>(cmp-spec-spy)'), 'x')
|
||||
assert.are.equal(cursor[2], 11)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('get_cursor_before_line', function()
|
||||
before_each(spec.before)
|
||||
it('insert-mode', function()
|
||||
local cursor_before_line
|
||||
feedkeys.call(keymap.t('i\t1234567890<Left><Left>'), 'nx', function()
|
||||
cursor_before_line = api.get_cursor_before_line()
|
||||
end)
|
||||
assert.are.same(cursor_before_line, '\t12345678')
|
||||
end)
|
||||
it('cmdline-mode', function()
|
||||
local cursor_before_line
|
||||
keymap.set_map(0, 'c', '<Plug>(cmp-spec-spy)', function()
|
||||
cursor_before_line = api.get_cursor_before_line()
|
||||
end, { expr = true, noremap = true })
|
||||
feedkeys.call(keymap.t(':\t1234567890<Left><Left>'), 'n')
|
||||
feedkeys.call(keymap.t('<Plug>(cmp-spec-spy)'), 'x')
|
||||
assert.are.same(cursor_before_line, '\t12345678')
|
||||
end)
|
||||
end)
|
||||
end)
|
101
bundle/nvim-cmp/lua/cmp/utils/async.lua
Normal file
101
bundle/nvim-cmp/lua/cmp/utils/async.lua
Normal file
@ -0,0 +1,101 @@
|
||||
local async = {}
|
||||
|
||||
---@class cmp.AsyncThrottle
|
||||
---@field public timeout number
|
||||
---@field public stop function
|
||||
---@field public __call function
|
||||
|
||||
---@param fn function
|
||||
---@param timeout number
|
||||
---@return cmp.AsyncThrottle
|
||||
async.throttle = function(fn, timeout)
|
||||
local time = nil
|
||||
local timer = vim.loop.new_timer()
|
||||
return setmetatable({
|
||||
timeout = timeout,
|
||||
stop = function()
|
||||
time = nil
|
||||
timer:stop()
|
||||
end,
|
||||
}, {
|
||||
__call = function(self, ...)
|
||||
local args = { ... }
|
||||
|
||||
if time == nil then
|
||||
time = vim.loop.now()
|
||||
end
|
||||
timer:stop()
|
||||
|
||||
local delta = math.max(1, self.timeout - (vim.loop.now() - time))
|
||||
timer:start(delta, 0, function()
|
||||
time = nil
|
||||
fn(unpack(args))
|
||||
end)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---Control async tasks.
|
||||
async.step = function(...)
|
||||
local tasks = { ... }
|
||||
local next
|
||||
next = function(...)
|
||||
if #tasks > 0 then
|
||||
table.remove(tasks, 1)(next, ...)
|
||||
end
|
||||
end
|
||||
table.remove(tasks, 1)(next)
|
||||
end
|
||||
|
||||
---Timeout callback function
|
||||
---@param fn function
|
||||
---@param timeout number
|
||||
---@return function
|
||||
async.timeout = function(fn, timeout)
|
||||
local timer
|
||||
local done = false
|
||||
local callback = function(...)
|
||||
if not done then
|
||||
done = true
|
||||
timer:stop()
|
||||
timer:close()
|
||||
fn(...)
|
||||
end
|
||||
end
|
||||
timer = vim.loop.new_timer()
|
||||
timer:start(timeout, 0, function()
|
||||
callback()
|
||||
end)
|
||||
return callback
|
||||
end
|
||||
|
||||
---@alias cmp.AsyncDedup fun(callback: function): function
|
||||
|
||||
---Create deduplicated callback
|
||||
---@return function
|
||||
async.dedup = function()
|
||||
local id = 0
|
||||
return function(callback)
|
||||
id = id + 1
|
||||
|
||||
local current = id
|
||||
return function(...)
|
||||
if current == id then
|
||||
callback(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Convert async process as sync
|
||||
async.sync = function(runner, timeout)
|
||||
local done = false
|
||||
runner(function()
|
||||
done = true
|
||||
end)
|
||||
vim.wait(timeout, function()
|
||||
return done
|
||||
end, 10, false)
|
||||
end
|
||||
|
||||
return async
|
69
bundle/nvim-cmp/lua/cmp/utils/async_spec.lua
Normal file
69
bundle/nvim-cmp/lua/cmp/utils/async_spec.lua
Normal file
@ -0,0 +1,69 @@
|
||||
local async = require('cmp.utils.async')
|
||||
|
||||
describe('utils.async', function()
|
||||
it('throttle', function()
|
||||
local count = 0
|
||||
local now
|
||||
local f = async.throttle(function()
|
||||
count = count + 1
|
||||
end, 100)
|
||||
|
||||
-- 1. delay for 100ms
|
||||
now = vim.loop.now()
|
||||
f.timeout = 100
|
||||
f()
|
||||
vim.wait(1000, function()
|
||||
return count == 1
|
||||
end)
|
||||
assert.is.truthy(math.abs(f.timeout - (vim.loop.now() - now)) < 10)
|
||||
|
||||
-- 2. delay for 500ms
|
||||
now = vim.loop.now()
|
||||
f.timeout = 500
|
||||
f()
|
||||
vim.wait(1000, function()
|
||||
return count == 2
|
||||
end)
|
||||
assert.is.truthy(math.abs(f.timeout - (vim.loop.now() - now)) < 10)
|
||||
|
||||
-- 4. delay for 500ms and wait 100ms (remain 400ms)
|
||||
f.timeout = 500
|
||||
f()
|
||||
vim.wait(100) -- remain 400ms
|
||||
|
||||
-- 5. call immediately (100ms already elapsed from No.4)
|
||||
now = vim.loop.now()
|
||||
f.timeout = 100
|
||||
f()
|
||||
vim.wait(1000, function()
|
||||
return count == 3
|
||||
end)
|
||||
assert.is.truthy(math.abs(vim.loop.now() - now) < 10)
|
||||
end)
|
||||
it('step', function()
|
||||
local done = false
|
||||
local step = {}
|
||||
async.step(function(next)
|
||||
vim.defer_fn(function()
|
||||
table.insert(step, 1)
|
||||
next()
|
||||
end, 10)
|
||||
end, function(next)
|
||||
vim.defer_fn(function()
|
||||
table.insert(step, 2)
|
||||
next()
|
||||
end, 10)
|
||||
end, function(next)
|
||||
vim.defer_fn(function()
|
||||
table.insert(step, 3)
|
||||
next()
|
||||
end, 10)
|
||||
end, function()
|
||||
done = true
|
||||
end)
|
||||
vim.wait(1000, function()
|
||||
return done
|
||||
end)
|
||||
assert.are.same(step, { 1, 2, 3 })
|
||||
end)
|
||||
end)
|
35
bundle/nvim-cmp/lua/cmp/utils/autocmd.lua
Normal file
35
bundle/nvim-cmp/lua/cmp/utils/autocmd.lua
Normal file
@ -0,0 +1,35 @@
|
||||
local debug = require('cmp.utils.debug')
|
||||
|
||||
local autocmd = {}
|
||||
|
||||
autocmd.events = {}
|
||||
|
||||
---Subscribe autocmd
|
||||
---@param event string
|
||||
---@param callback function
|
||||
---@return function
|
||||
autocmd.subscribe = function(event, callback)
|
||||
autocmd.events[event] = autocmd.events[event] or {}
|
||||
table.insert(autocmd.events[event], callback)
|
||||
return function()
|
||||
for i, callback_ in ipairs(autocmd.events[event]) do
|
||||
if callback_ == callback then
|
||||
table.remove(autocmd.events[event], i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Emit autocmd
|
||||
---@param event string
|
||||
autocmd.emit = function(event)
|
||||
debug.log(' ')
|
||||
debug.log(string.format('>>> %s', event))
|
||||
autocmd.events[event] = autocmd.events[event] or {}
|
||||
for _, callback in ipairs(autocmd.events[event]) do
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
return autocmd
|
33
bundle/nvim-cmp/lua/cmp/utils/binary.lua
Normal file
33
bundle/nvim-cmp/lua/cmp/utils/binary.lua
Normal file
@ -0,0 +1,33 @@
|
||||
local binary = {}
|
||||
|
||||
---Insert item to list to ordered index
|
||||
---@param list any[]
|
||||
---@param item any
|
||||
---@param func fun(a: any, b: any): "1"|"-1"|"0"
|
||||
binary.insort = function(list, item, func)
|
||||
table.insert(list, binary.search(list, item, func), item)
|
||||
end
|
||||
|
||||
---Search suitable index from list
|
||||
---@param list any[]
|
||||
---@param item any
|
||||
---@param func fun(a: any, b: any): "1"|"-1"|"0"
|
||||
---@return number
|
||||
binary.search = function(list, item, func)
|
||||
local s = 1
|
||||
local e = #list
|
||||
while s <= e do
|
||||
local idx = math.floor((e + s) / 2)
|
||||
local diff = func(item, list[idx])
|
||||
if diff > 0 then
|
||||
s = idx + 1
|
||||
elseif diff < 0 then
|
||||
e = idx - 1
|
||||
else
|
||||
return idx + 1
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
return binary
|
28
bundle/nvim-cmp/lua/cmp/utils/binary_spec.lua
Normal file
28
bundle/nvim-cmp/lua/cmp/utils/binary_spec.lua
Normal file
@ -0,0 +1,28 @@
|
||||
local binary = require('cmp.utils.binary')
|
||||
|
||||
describe('utils.binary', function()
|
||||
it('insort', function()
|
||||
local func = function(a, b)
|
||||
return a.score - b.score
|
||||
end
|
||||
local list = {}
|
||||
binary.insort(list, { id = 'a', score = 1 }, func)
|
||||
binary.insort(list, { id = 'b', score = 5 }, func)
|
||||
binary.insort(list, { id = 'c', score = 2.5 }, func)
|
||||
binary.insort(list, { id = 'd', score = 2 }, func)
|
||||
binary.insort(list, { id = 'e', score = 8 }, func)
|
||||
binary.insort(list, { id = 'g', score = 8 }, func)
|
||||
binary.insort(list, { id = 'h', score = 7 }, func)
|
||||
binary.insort(list, { id = 'i', score = 6 }, func)
|
||||
binary.insort(list, { id = 'j', score = 4 }, func)
|
||||
assert.are.equal(list[1].id, 'a')
|
||||
assert.are.equal(list[2].id, 'd')
|
||||
assert.are.equal(list[3].id, 'c')
|
||||
assert.are.equal(list[4].id, 'j')
|
||||
assert.are.equal(list[5].id, 'b')
|
||||
assert.are.equal(list[6].id, 'i')
|
||||
assert.are.equal(list[7].id, 'h')
|
||||
assert.are.equal(list[8].id, 'e')
|
||||
assert.are.equal(list[9].id, 'g')
|
||||
end)
|
||||
end)
|
17
bundle/nvim-cmp/lua/cmp/utils/buffer.lua
Normal file
17
bundle/nvim-cmp/lua/cmp/utils/buffer.lua
Normal file
@ -0,0 +1,17 @@
|
||||
local buffer = {}
|
||||
|
||||
buffer.ensure = setmetatable({
|
||||
cache = {},
|
||||
}, {
|
||||
__call = function(self, name)
|
||||
if not (self.cache[name] and vim.api.nvim_buf_is_valid(self.cache[name])) then
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_option(buf, 'buftype', 'nofile')
|
||||
vim.api.nvim_buf_set_option(buf, 'bufhidden', 'hide')
|
||||
self.cache[name] = buf
|
||||
end
|
||||
return self.cache[name]
|
||||
end,
|
||||
})
|
||||
|
||||
return buffer
|
58
bundle/nvim-cmp/lua/cmp/utils/cache.lua
Normal file
58
bundle/nvim-cmp/lua/cmp/utils/cache.lua
Normal file
@ -0,0 +1,58 @@
|
||||
---@class cmp.Cache
|
||||
---@field public entries any
|
||||
local cache = {}
|
||||
|
||||
cache.new = function()
|
||||
local self = setmetatable({}, { __index = cache })
|
||||
self.entries = {}
|
||||
return self
|
||||
end
|
||||
|
||||
---Get cache value
|
||||
---@param key string
|
||||
---@return any|nil
|
||||
cache.get = function(self, key)
|
||||
key = self:key(key)
|
||||
if self.entries[key] ~= nil then
|
||||
return self.entries[key]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---Set cache value explicitly
|
||||
---@param key string
|
||||
---@vararg any
|
||||
cache.set = function(self, key, value)
|
||||
key = self:key(key)
|
||||
self.entries[key] = value
|
||||
end
|
||||
|
||||
---Ensure value by callback
|
||||
---@param key string
|
||||
---@param callback fun(): any
|
||||
cache.ensure = function(self, key, callback)
|
||||
local value = self:get(key)
|
||||
if value == nil then
|
||||
local v = callback()
|
||||
self:set(key, v)
|
||||
return v
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
---Clear all cache entries
|
||||
cache.clear = function(self)
|
||||
self.entries = {}
|
||||
end
|
||||
|
||||
---Create key
|
||||
---@param key string|table
|
||||
---@return string
|
||||
cache.key = function(_, key)
|
||||
if type(key) == 'table' then
|
||||
return table.concat(key, ':')
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
return cache
|
115
bundle/nvim-cmp/lua/cmp/utils/char.lua
Normal file
115
bundle/nvim-cmp/lua/cmp/utils/char.lua
Normal file
@ -0,0 +1,115 @@
|
||||
local alpha = {}
|
||||
string.gsub('abcdefghijklmnopqrstuvwxyz', '.', function(char)
|
||||
alpha[string.byte(char)] = true
|
||||
end)
|
||||
|
||||
local ALPHA = {}
|
||||
string.gsub('ABCDEFGHIJKLMNOPQRSTUVWXYZ', '.', function(char)
|
||||
ALPHA[string.byte(char)] = true
|
||||
end)
|
||||
|
||||
local digit = {}
|
||||
string.gsub('1234567890', '.', function(char)
|
||||
digit[string.byte(char)] = true
|
||||
end)
|
||||
|
||||
local white = {}
|
||||
string.gsub(' \t\n', '.', function(char)
|
||||
white[string.byte(char)] = true
|
||||
end)
|
||||
|
||||
local char = {}
|
||||
|
||||
---@param byte number
|
||||
---@return boolean
|
||||
char.is_upper = function(byte)
|
||||
return ALPHA[byte]
|
||||
end
|
||||
|
||||
---@param byte number
|
||||
---@return boolean
|
||||
char.is_alpha = function(byte)
|
||||
return alpha[byte] or ALPHA[byte]
|
||||
end
|
||||
|
||||
---@param byte number
|
||||
---@return boolean
|
||||
char.is_digit = function(byte)
|
||||
return digit[byte]
|
||||
end
|
||||
|
||||
---@param byte number
|
||||
---@return boolean
|
||||
char.is_white = function(byte)
|
||||
return white[byte]
|
||||
end
|
||||
|
||||
---@param byte number
|
||||
---@return boolean
|
||||
char.is_symbol = function(byte)
|
||||
return not (char.is_alnum(byte) or char.is_white(byte))
|
||||
end
|
||||
|
||||
---@param byte number
|
||||
---@return boolean
|
||||
char.is_printable = function(byte)
|
||||
return string.match(string.char(byte), '^%c$') == nil
|
||||
end
|
||||
|
||||
---@param byte number
|
||||
---@return boolean
|
||||
char.is_alnum = function(byte)
|
||||
return char.is_alpha(byte) or char.is_digit(byte)
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param index number
|
||||
---@return boolean
|
||||
char.is_semantic_index = function(text, index)
|
||||
if index <= 1 then
|
||||
return true
|
||||
end
|
||||
|
||||
local prev = string.byte(text, index - 1)
|
||||
local curr = string.byte(text, index)
|
||||
|
||||
if not char.is_upper(prev) and char.is_upper(curr) then
|
||||
return true
|
||||
end
|
||||
if char.is_symbol(curr) or char.is_white(curr) then
|
||||
return true
|
||||
end
|
||||
if not char.is_alpha(prev) and char.is_alpha(curr) then
|
||||
return true
|
||||
end
|
||||
if not char.is_digit(prev) and char.is_digit(curr) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param current_index number
|
||||
---@return boolean
|
||||
char.get_next_semantic_index = function(text, current_index)
|
||||
for i = current_index + 1, #text do
|
||||
if char.is_semantic_index(text, i) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #text + 1
|
||||
end
|
||||
|
||||
---Ignore case match
|
||||
---@param byte1 number
|
||||
---@param byte2 number
|
||||
---@return boolean
|
||||
char.match = function(byte1, byte2)
|
||||
if not char.is_alpha(byte1) or not char.is_alpha(byte2) then
|
||||
return byte1 == byte2
|
||||
end
|
||||
local diff = byte1 - byte2
|
||||
return diff == 0 or diff == 32 or diff == -32
|
||||
end
|
||||
|
||||
return char
|
20
bundle/nvim-cmp/lua/cmp/utils/debug.lua
Normal file
20
bundle/nvim-cmp/lua/cmp/utils/debug.lua
Normal file
@ -0,0 +1,20 @@
|
||||
local debug = {}
|
||||
|
||||
debug.flag = false
|
||||
|
||||
---Print log
|
||||
---@vararg any
|
||||
debug.log = function(...)
|
||||
if debug.flag then
|
||||
local data = {}
|
||||
for _, v in ipairs({ ... }) do
|
||||
if not vim.tbl_contains({ 'string', 'number', 'boolean' }, type(v)) then
|
||||
v = vim.inspect(v)
|
||||
end
|
||||
table.insert(data, v)
|
||||
end
|
||||
print(table.concat(data, '\t'))
|
||||
end
|
||||
end
|
||||
|
||||
return debug
|
51
bundle/nvim-cmp/lua/cmp/utils/event.lua
Normal file
51
bundle/nvim-cmp/lua/cmp/utils/event.lua
Normal file
@ -0,0 +1,51 @@
|
||||
---@class cmp.Event
|
||||
---@field private events table<string, function[]>
|
||||
local event = {}
|
||||
|
||||
---Create vents
|
||||
event.new = function()
|
||||
local self = setmetatable({}, { __index = event })
|
||||
self.events = {}
|
||||
return self
|
||||
end
|
||||
|
||||
---Add event listener
|
||||
---@param name string
|
||||
---@param callback function
|
||||
---@return function
|
||||
event.on = function(self, name, callback)
|
||||
if not self.events[name] then
|
||||
self.events[name] = {}
|
||||
end
|
||||
table.insert(self.events[name], callback)
|
||||
return function()
|
||||
self:off(name, callback)
|
||||
end
|
||||
end
|
||||
|
||||
---Remove event listener
|
||||
---@param name string
|
||||
---@param callback function
|
||||
event.off = function(self, name, callback)
|
||||
for i, callback_ in ipairs(self.events[name] or {}) do
|
||||
if callback_ == callback then
|
||||
table.remove(self.events[name], i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Remove all events
|
||||
event.clear = function(self)
|
||||
self.events = {}
|
||||
end
|
||||
|
||||
---Emit event
|
||||
---@param name string
|
||||
event.emit = function(self, name, ...)
|
||||
for _, callback in ipairs(self.events[name] or {}) do
|
||||
callback(...)
|
||||
end
|
||||
end
|
||||
|
||||
return event
|
110
bundle/nvim-cmp/lua/cmp/utils/feedkeys.lua
Normal file
110
bundle/nvim-cmp/lua/cmp/utils/feedkeys.lua
Normal file
@ -0,0 +1,110 @@
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local misc = require('cmp.utils.misc')
|
||||
|
||||
local feedkeys = {}
|
||||
|
||||
feedkeys.call = setmetatable({
|
||||
callbacks = {},
|
||||
}, {
|
||||
__call = function(self, keys, mode, callback)
|
||||
if vim.fn.reg_recording() ~= '' then
|
||||
return feedkeys.call_macro(keys, mode, callback)
|
||||
end
|
||||
|
||||
local is_insert = string.match(mode, 'i') ~= nil
|
||||
local is_immediate = string.match(mode, 'x') ~= nil
|
||||
|
||||
local queue = {}
|
||||
if #keys > 0 then
|
||||
table.insert(queue, { keymap.t('<Cmd>set lazyredraw<CR>'), 'n' })
|
||||
table.insert(queue, { keymap.t('<Cmd>set textwidth=0<CR>'), 'n' })
|
||||
table.insert(queue, { keymap.t('<Cmd>set eventignore=all<CR>'), 'n' })
|
||||
table.insert(queue, { keys, string.gsub(mode, '[itx]', ''), true })
|
||||
table.insert(queue, { keymap.t('<Cmd>set %slazyredraw<CR>'):format(vim.o.lazyredraw and '' or 'no'), 'n' })
|
||||
table.insert(queue, { keymap.t('<Cmd>set textwidth=%s<CR>'):format(vim.bo.textwidth or 0), 'n' })
|
||||
table.insert(queue, { keymap.t('<Cmd>set eventignore=%s<CR>'):format(vim.o.eventignore or ''), 'n' })
|
||||
end
|
||||
|
||||
if callback then
|
||||
local id = misc.id('cmp.utils.feedkeys.call')
|
||||
self.callbacks[id] = callback
|
||||
table.insert(queue, { keymap.t('<Cmd>call v:lua.cmp.utils.feedkeys.call.run(%s)<CR>'):format(id), 'n', true })
|
||||
end
|
||||
|
||||
if is_insert then
|
||||
for i = #queue, 1, -1 do
|
||||
vim.api.nvim_feedkeys(queue[i][1], queue[i][2] .. 'i', queue[i][3])
|
||||
end
|
||||
else
|
||||
for i = 1, #queue do
|
||||
vim.api.nvim_feedkeys(queue[i][1], queue[i][2], queue[i][3])
|
||||
end
|
||||
end
|
||||
|
||||
if is_immediate then
|
||||
vim.api.nvim_feedkeys('', 'x', true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
misc.set(_G, { 'cmp', 'utils', 'feedkeys', 'call', 'run' }, function(id)
|
||||
if feedkeys.call.callbacks[id] then
|
||||
feedkeys.call.callbacks[id]()
|
||||
feedkeys.call.callbacks[id] = nil
|
||||
end
|
||||
return ''
|
||||
end)
|
||||
|
||||
feedkeys.call_macro = setmetatable({
|
||||
queue = {},
|
||||
current = nil,
|
||||
timer = vim.loop.new_timer(),
|
||||
running = false,
|
||||
}, {
|
||||
__call = function(self, keys, mode, callback)
|
||||
local is_insert = string.match(mode, 'i') ~= nil
|
||||
table.insert(self.queue, is_insert and 1 or #self.queue + 1, {
|
||||
keys = keys,
|
||||
mode = mode,
|
||||
callback = callback,
|
||||
})
|
||||
|
||||
if not self.running then
|
||||
self.running = true
|
||||
local consume
|
||||
consume = vim.schedule_wrap(function()
|
||||
if vim.fn.getchar(1) == 0 then
|
||||
if self.current then
|
||||
vim.cmd(('set backspace=%s'):format(self.current.backspace or ''))
|
||||
vim.cmd(('set eventignore=%s'):format(self.current.eventignore or ''))
|
||||
if self.current.callback then
|
||||
self.current.callback()
|
||||
end
|
||||
self.current = nil
|
||||
end
|
||||
|
||||
local current = table.remove(self.queue, 1)
|
||||
if current then
|
||||
self.current = {
|
||||
keys = current.keys,
|
||||
callback = current.callback,
|
||||
backspace = vim.o.backspace,
|
||||
eventignore = vim.o.eventignore,
|
||||
}
|
||||
vim.api.nvim_feedkeys(keymap.t('<Cmd>set backspace=start<CR>'), 'n', true)
|
||||
vim.api.nvim_feedkeys(keymap.t('<Cmd>set eventignore=all<CR>'), 'n', true)
|
||||
vim.api.nvim_feedkeys(current.keys, string.gsub(current.mode, '[i]', ''), true) -- 'i' flag is manually resolved.
|
||||
end
|
||||
end
|
||||
|
||||
if #self.queue ~= 0 or self.current then
|
||||
vim.defer_fn(consume, 1)
|
||||
else
|
||||
self.running = false
|
||||
end
|
||||
end)
|
||||
vim.defer_fn(consume, 1)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return feedkeys
|
56
bundle/nvim-cmp/lua/cmp/utils/feedkeys_spec.lua
Normal file
56
bundle/nvim-cmp/lua/cmp/utils/feedkeys_spec.lua
Normal file
@ -0,0 +1,56 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
|
||||
describe('feedkeys', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('dot-repeat', function()
|
||||
local reg
|
||||
feedkeys.call(keymap.t('iaiueo<Esc>'), 'nx', function()
|
||||
reg = vim.fn.getreg('.')
|
||||
end)
|
||||
assert.are.equal(reg, keymap.t('aiueo'))
|
||||
end)
|
||||
|
||||
it('textwidth', function()
|
||||
vim.cmd([[setlocal textwidth=6]])
|
||||
feedkeys.call(keymap.t('iaiueo '), 'nx')
|
||||
feedkeys.call(keymap.t('aaiueoaiueo'), 'nx')
|
||||
assert.are.same(vim.api.nvim_buf_get_lines(0, 0, -1, false), {
|
||||
'aiueo aiueoaiueo',
|
||||
})
|
||||
end)
|
||||
|
||||
it('autoindent', function()
|
||||
vim.cmd([[setlocal indentkeys+==end]])
|
||||
feedkeys.call(keymap.t('iif<CR><Tab>end') .. keymap.autoindent(), 'nx')
|
||||
assert.are.same(vim.api.nvim_buf_get_lines(0, 0, -1, false), {
|
||||
'if',
|
||||
'end',
|
||||
})
|
||||
end)
|
||||
|
||||
it('testability', function()
|
||||
feedkeys.call('i', 'n', function()
|
||||
feedkeys.call('', 'n', function()
|
||||
feedkeys.call('aiueo', 'in')
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
feedkeys.call(keymap.t('<BS><BS><BS><BS><BS>'), 'in')
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
feedkeys.call(keymap.t('abcde'), 'in')
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
feedkeys.call(keymap.t('<BS><BS><BS><BS><BS>'), 'in')
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
feedkeys.call(keymap.t('12345'), 'in')
|
||||
end)
|
||||
end)
|
||||
feedkeys.call('', 'x')
|
||||
assert.are.same(vim.api.nvim_buf_get_lines(0, 0, -1, false), { '12345' })
|
||||
end)
|
||||
end)
|
46
bundle/nvim-cmp/lua/cmp/utils/highlight.lua
Normal file
46
bundle/nvim-cmp/lua/cmp/utils/highlight.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local highlight = {}
|
||||
|
||||
highlight.keys = {
|
||||
'gui',
|
||||
'guifg',
|
||||
'guibg',
|
||||
'cterm',
|
||||
'ctermfg',
|
||||
'ctermbg',
|
||||
}
|
||||
|
||||
highlight.inherit = function(name, source, override)
|
||||
local cmd = ('highlight! default %s'):format(name)
|
||||
for _, key in ipairs(highlight.keys) do
|
||||
if override[key] then
|
||||
cmd = cmd .. (' %s=%s'):format(key, override[key])
|
||||
else
|
||||
local v = highlight.get(source, key)
|
||||
v = v == '' and 'NONE' or v
|
||||
cmd = cmd .. (' %s=%s'):format(key, v)
|
||||
end
|
||||
end
|
||||
vim.cmd(cmd)
|
||||
end
|
||||
|
||||
highlight.get = function(source, key)
|
||||
if key == 'gui' or key == 'cterm' then
|
||||
local ui = {}
|
||||
for _, k in ipairs({ 'bold', 'italic', 'reverse', 'inverse', 'standout', 'underline', 'undercurl', 'strikethrough' }) do
|
||||
if vim.fn.synIDattr(vim.fn.hlID(source), k, key) == 1 then
|
||||
table.insert(ui, k)
|
||||
end
|
||||
end
|
||||
return table.concat(ui, ',')
|
||||
elseif key == 'guifg' then
|
||||
return vim.fn.synIDattr(vim.fn.hlID(source), 'fg#', 'gui')
|
||||
elseif key == 'guibg' then
|
||||
return vim.fn.synIDattr(vim.fn.hlID(source), 'bg#', 'gui')
|
||||
elseif key == 'ctermfg' then
|
||||
return vim.fn.synIDattr(vim.fn.hlID(source), 'fg', 'term')
|
||||
elseif key == 'ctermbg' then
|
||||
return vim.fn.synIDattr(vim.fn.hlID(source), 'bg', 'term')
|
||||
end
|
||||
end
|
||||
|
||||
return highlight
|
267
bundle/nvim-cmp/lua/cmp/utils/keymap.lua
Normal file
267
bundle/nvim-cmp/lua/cmp/utils/keymap.lua
Normal file
@ -0,0 +1,267 @@
|
||||
local misc = require('cmp.utils.misc')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
local keymap = {}
|
||||
|
||||
---Shortcut for nvim_replace_termcodes
|
||||
---@param keys string
|
||||
---@return string
|
||||
keymap.t = function(keys)
|
||||
return vim.api.nvim_replace_termcodes(keys, true, true, true)
|
||||
end
|
||||
|
||||
---Normalize key sequence.
|
||||
---@param keys string
|
||||
---@return string
|
||||
keymap.normalize = function(keys)
|
||||
vim.api.nvim_set_keymap('t', '<Plug>(cmp.utils.keymap.normalize)', keys, {})
|
||||
for _, map in ipairs(vim.api.nvim_get_keymap('t')) do
|
||||
if keymap.equals(map.lhs, '<Plug>(cmp.utils.keymap.normalize)') then
|
||||
return map.rhs
|
||||
end
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
---Return vim notation keymapping (simple conversion).
|
||||
---@param s string
|
||||
---@return string
|
||||
keymap.to_keymap = setmetatable({
|
||||
['<CR>'] = { '\n', '\r', '\r\n' },
|
||||
['<Tab>'] = { '\t' },
|
||||
['<BSlash>'] = { '\\' },
|
||||
['<Bar>'] = { '|' },
|
||||
['<Space>'] = { ' ' },
|
||||
}, {
|
||||
__call = function(self, s)
|
||||
return string.gsub(s, '.', function(c)
|
||||
for key, chars in pairs(self) do
|
||||
if vim.tbl_contains(chars, c) then
|
||||
return key
|
||||
end
|
||||
end
|
||||
return c
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
---Mode safe break undo
|
||||
keymap.undobreak = function()
|
||||
if not api.is_insert_mode() then
|
||||
return ''
|
||||
end
|
||||
return keymap.t('<C-g>u')
|
||||
end
|
||||
|
||||
---Mode safe join undo
|
||||
keymap.undojoin = function()
|
||||
if not api.is_insert_mode() then
|
||||
return ''
|
||||
end
|
||||
return keymap.t('<C-g>U')
|
||||
end
|
||||
|
||||
---Create backspace keys.
|
||||
---@param count number
|
||||
---@return string
|
||||
keymap.backspace = function(count)
|
||||
if count <= 0 then
|
||||
return ''
|
||||
end
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.t(string.rep('<BS>', count)))
|
||||
return table.concat(keys, '')
|
||||
end
|
||||
|
||||
---Create autoindent keys
|
||||
---@return string
|
||||
keymap.autoindent = function()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal cindent<CR>'))
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal indentkeys+=!^F<CR>'))
|
||||
table.insert(keys, keymap.t('<C-f>'))
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal %scindent<CR>'):format(vim.bo.cindent and '' or 'no'))
|
||||
table.insert(keys, keymap.t('<Cmd>setlocal indentkeys=%s<CR>'):format(vim.bo.indentkeys:gsub('|', '\\|')))
|
||||
return table.concat(keys, '')
|
||||
end
|
||||
|
||||
---Return two key sequence are equal or not.
|
||||
---@param a string
|
||||
---@param b string
|
||||
---@return boolean
|
||||
keymap.equals = function(a, b)
|
||||
return keymap.t(a) == keymap.t(b)
|
||||
end
|
||||
|
||||
---Register keypress handler.
|
||||
keymap.listen = function(mode, lhs, callback)
|
||||
lhs = keymap.normalize(keymap.to_keymap(lhs))
|
||||
|
||||
local existing = keymap.get_mapping(mode, lhs)
|
||||
local id = string.match(existing.rhs, 'v:lua%.cmp%.utils%.keymap%.set_map%((%d+)%)')
|
||||
if id and keymap.set_map.callbacks[tonumber(id, 10)] then
|
||||
return
|
||||
end
|
||||
|
||||
local bufnr = existing.buffer and vim.api.nvim_get_current_buf() or -1
|
||||
local fallback = keymap.evacuate(bufnr, mode, lhs)
|
||||
keymap.set_map(bufnr, mode, lhs, function()
|
||||
if mode == 'c' and vim.fn.getcmdtype() == '=' then
|
||||
return vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true)
|
||||
end
|
||||
|
||||
callback(
|
||||
lhs,
|
||||
misc.once(function()
|
||||
vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true)
|
||||
end)
|
||||
)
|
||||
end, {
|
||||
expr = false,
|
||||
noremap = true,
|
||||
silent = true,
|
||||
})
|
||||
end
|
||||
|
||||
---Get mapping
|
||||
---@param mode string
|
||||
---@param lhs string
|
||||
---@return table
|
||||
keymap.get_mapping = function(mode, lhs)
|
||||
lhs = keymap.normalize(lhs)
|
||||
|
||||
for _, map in ipairs(vim.api.nvim_buf_get_keymap(0, mode)) do
|
||||
if keymap.equals(map.lhs, lhs) then
|
||||
return {
|
||||
lhs = map.lhs,
|
||||
rhs = map.rhs,
|
||||
expr = map.expr == 1,
|
||||
noremap = map.noremap == 1,
|
||||
script = map.script == 1,
|
||||
silent = map.silent == 1,
|
||||
nowait = map.nowait == 1,
|
||||
buffer = true,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
for _, map in ipairs(vim.api.nvim_get_keymap(mode)) do
|
||||
if keymap.equals(map.lhs, lhs) then
|
||||
return {
|
||||
lhs = map.lhs,
|
||||
rhs = map.rhs,
|
||||
expr = map.expr == 1,
|
||||
noremap = map.noremap == 1,
|
||||
script = map.script == 1,
|
||||
silent = map.silent == 1,
|
||||
nowait = map.nowait == 1,
|
||||
buffer = false,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
lhs = lhs,
|
||||
rhs = lhs,
|
||||
expr = false,
|
||||
noremap = true,
|
||||
script = false,
|
||||
silent = false,
|
||||
nowait = false,
|
||||
buffer = false,
|
||||
}
|
||||
end
|
||||
|
||||
---Evacuate existing key mapping
|
||||
---@param bufnr number
|
||||
---@param mode string
|
||||
---@param lhs string
|
||||
---@return { keys: string, mode: string }
|
||||
keymap.evacuate = function(bufnr, mode, lhs)
|
||||
local map = keymap.get_mapping(mode, lhs)
|
||||
if not map then
|
||||
return { keys = lhs, mode = 'itn' }
|
||||
end
|
||||
|
||||
-- Keep existing mapping as <Plug> mapping. We escape fisrt recursive key sequence. See `:help recursive_mapping`)
|
||||
local rhs = map.rhs
|
||||
if not map.noremap and map.expr then
|
||||
-- remap & expr mapping should evacuate as <Plug> mapping with solving recursive mapping.
|
||||
rhs = function()
|
||||
return keymap.t(keymap.recursive(bufnr, mode, lhs, vim.api.nvim_eval(map.rhs)))
|
||||
end
|
||||
elseif map.noremap and map.expr then
|
||||
-- noremap & expr mapping should always evacuate as <Plug> mapping.
|
||||
rhs = rhs
|
||||
elseif map.script then
|
||||
-- script mapping should always evacuate as <Plug> mapping.
|
||||
rhs = rhs
|
||||
elseif not map.noremap then
|
||||
-- remap & non-expr mapping should be checked if recursive or not.
|
||||
rhs = keymap.recursive(bufnr, mode, lhs, rhs)
|
||||
if keymap.equals(rhs, map.rhs) or map.noremap then
|
||||
return { keys = rhs, mode = 'it' .. (map.noremap and 'n' or '') }
|
||||
end
|
||||
else
|
||||
-- noremap & non-expr mapping doesn't need to evacuate.
|
||||
return { keys = rhs, mode = 'it' .. (map.noremap and 'n' or '') }
|
||||
end
|
||||
|
||||
local fallback = ('<Plug>(cmp.utils.keymap.evacuate:%s)'):format(map.lhs)
|
||||
keymap.set_map(bufnr, mode, fallback, rhs, {
|
||||
expr = map.expr,
|
||||
noremap = map.noremap,
|
||||
script = map.script,
|
||||
silent = mode ~= 'c', -- I can't understand but it solves the #427 (wilder.nvim's mapping does not work if silent=true in cmdline mode...)
|
||||
})
|
||||
return { keys = fallback, mode = 'it' }
|
||||
end
|
||||
|
||||
---Solve recursive mapping
|
||||
---@param bufnr number
|
||||
---@param mode string
|
||||
---@param lhs string
|
||||
---@param rhs string
|
||||
---@return string
|
||||
keymap.recursive = function(bufnr, mode, lhs, rhs)
|
||||
rhs = keymap.normalize(rhs)
|
||||
|
||||
local recursive_lhs = ('<Plug>(cmp.utils.keymap.recursive:%s)'):format(lhs)
|
||||
local recursive_rhs = string.gsub(rhs, '^' .. vim.pesc(keymap.normalize(lhs)), recursive_lhs)
|
||||
if not keymap.equals(recursive_rhs, rhs) then
|
||||
keymap.set_map(bufnr, mode, recursive_lhs, lhs, {
|
||||
expr = false,
|
||||
noremap = true,
|
||||
silent = true,
|
||||
})
|
||||
end
|
||||
return recursive_rhs
|
||||
end
|
||||
|
||||
---Set keymapping
|
||||
keymap.set_map = setmetatable({
|
||||
callbacks = {},
|
||||
}, {
|
||||
__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 = ('<Cmd>call v:lua.cmp.utils.keymap.set_map(%s)<CR>'):format(id)
|
||||
end
|
||||
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,
|
||||
})
|
||||
misc.set(_G, { 'cmp', 'utils', 'keymap', 'set_map' }, function(id)
|
||||
return keymap.set_map.callbacks[id]() or ''
|
||||
end)
|
||||
|
||||
return keymap
|
81
bundle/nvim-cmp/lua/cmp/utils/keymap_spec.lua
Normal file
81
bundle/nvim-cmp/lua/cmp/utils/keymap_spec.lua
Normal file
@ -0,0 +1,81 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
|
||||
describe('keymap', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('to_keymap', function()
|
||||
assert.are.equal(keymap.to_keymap('\n'), '<CR>')
|
||||
assert.are.equal(keymap.to_keymap('<CR>'), '<CR>')
|
||||
assert.are.equal(keymap.to_keymap('|'), '<Bar>')
|
||||
end)
|
||||
|
||||
describe('evacuate', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('expr & register', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', [['<C-r>="("<CR>']], {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
})
|
||||
local fallback = keymap.evacuate(0, 'i', '(')
|
||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
||||
assert.are.same({ '(' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
it('recursive & <Plug> (tpope/vim-endwise)', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Plug>(paren-close)', [[)<Left>]], {
|
||||
expr = false,
|
||||
noremap = true,
|
||||
})
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', [[(<Plug>(paren-close)]], {
|
||||
expr = false,
|
||||
noremap = false,
|
||||
})
|
||||
local fallback = keymap.evacuate(0, 'i', '(')
|
||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
||||
assert.are.same({ '()' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
describe('expr & recursive', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('true', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Tab>', [[v:true ? '<C-r>="foobar"<CR>' : '<Tab>aiueo']], {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
})
|
||||
local fallback = keymap.evacuate(0, 'i', '<Tab>')
|
||||
vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
|
||||
assert.are.same({ 'foobar' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
it('false', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Tab>', [[v:false ? '<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({ '\taiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
describe('realworld', function()
|
||||
before_each(spec.before)
|
||||
it('#226', function()
|
||||
keymap.listen('i', '<c-n>', function(_, fallback)
|
||||
fallback()
|
||||
end)
|
||||
vim.api.nvim_feedkeys(keymap.t('iaiueo<CR>a<C-n><C-n>'), 'tx', true)
|
||||
assert.are.same({ 'aiueo', 'aiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
it('#414', function()
|
||||
keymap.listen('i', '<M-j>', function()
|
||||
vim.api.nvim_feedkeys(keymap.t('<C-n>'), 'int', true)
|
||||
end)
|
||||
vim.api.nvim_feedkeys(keymap.t('iaiueo<CR>a<M-j><M-j>'), 'tx', true)
|
||||
assert.are.same({ 'aiueo', 'aiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
end)
|
||||
end)
|
181
bundle/nvim-cmp/lua/cmp/utils/misc.lua
Normal file
181
bundle/nvim-cmp/lua/cmp/utils/misc.lua
Normal file
@ -0,0 +1,181 @@
|
||||
local misc = {}
|
||||
|
||||
---Create once callback
|
||||
---@param callback function
|
||||
---@return function
|
||||
misc.once = function(callback)
|
||||
local done = false
|
||||
return function(...)
|
||||
if done then
|
||||
return
|
||||
end
|
||||
done = true
|
||||
callback(...)
|
||||
end
|
||||
end
|
||||
|
||||
---Return concatenated list
|
||||
---@param list1 any[]
|
||||
---@param list2 any[]
|
||||
---@return any[]
|
||||
misc.concat = function(list1, list2)
|
||||
local new_list = {}
|
||||
for _, v in ipairs(list1) do
|
||||
table.insert(new_list, v)
|
||||
end
|
||||
for _, v in ipairs(list2) do
|
||||
table.insert(new_list, v)
|
||||
end
|
||||
return new_list
|
||||
end
|
||||
|
||||
---The symbol to remove key in misc.merge.
|
||||
misc.none = vim.NIL
|
||||
|
||||
---Merge two tables recursively
|
||||
---@generic T
|
||||
---@param v1 T
|
||||
---@param v2 T
|
||||
---@return T
|
||||
misc.merge = function(v1, v2)
|
||||
local merge1 = type(v1) == 'table' and (not vim.tbl_islist(v1) or vim.tbl_isempty(v1))
|
||||
local merge2 = type(v2) == 'table' and (not vim.tbl_islist(v2) or vim.tbl_isempty(v2))
|
||||
if merge1 and merge2 then
|
||||
local new_tbl = {}
|
||||
for k, v in pairs(v2) do
|
||||
new_tbl[k] = misc.merge(v1[k], v)
|
||||
end
|
||||
for k, v in pairs(v1) do
|
||||
if v2[k] == nil and v ~= misc.none then
|
||||
new_tbl[k] = v
|
||||
end
|
||||
end
|
||||
return new_tbl
|
||||
end
|
||||
if v1 == misc.none then
|
||||
return nil
|
||||
end
|
||||
if v1 == nil then
|
||||
if v2 == misc.none then
|
||||
return nil
|
||||
else
|
||||
return v2
|
||||
end
|
||||
end
|
||||
if v1 == true then
|
||||
if merge2 then
|
||||
return v2
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
return v1
|
||||
end
|
||||
|
||||
---Generate id for group name
|
||||
misc.id = setmetatable({
|
||||
group = {},
|
||||
}, {
|
||||
__call = function(_, group)
|
||||
misc.id.group[group] = misc.id.group[group] or vim.loop.now()
|
||||
misc.id.group[group] = misc.id.group[group] + 1
|
||||
return misc.id.group[group]
|
||||
end,
|
||||
})
|
||||
|
||||
---Check the value is nil or not.
|
||||
---@param v boolean
|
||||
---@return boolean
|
||||
misc.safe = function(v)
|
||||
if v == nil or v == vim.NIL then
|
||||
return nil
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
---Treat 1/0 as bool value
|
||||
---@param v boolean|"1"|"0"
|
||||
---@param def boolean
|
||||
---@return boolean
|
||||
misc.bool = function(v, def)
|
||||
if misc.safe(v) == nil then
|
||||
return def
|
||||
end
|
||||
return v == true or v == 1
|
||||
end
|
||||
|
||||
---Set value to deep object
|
||||
---@param t table
|
||||
---@param keys string[]
|
||||
---@param v any
|
||||
misc.set = function(t, keys, v)
|
||||
local c = t
|
||||
for i = 1, #keys - 1 do
|
||||
local key = keys[i]
|
||||
c[key] = misc.safe(c[key]) or {}
|
||||
c = c[key]
|
||||
end
|
||||
c[keys[#keys]] = v
|
||||
end
|
||||
|
||||
---Copy table
|
||||
---@generic T
|
||||
---@param tbl T
|
||||
---@return T
|
||||
misc.copy = function(tbl)
|
||||
if type(tbl) ~= 'table' then
|
||||
return tbl
|
||||
end
|
||||
|
||||
if vim.tbl_islist(tbl) then
|
||||
local copy = {}
|
||||
for i, value in ipairs(tbl) do
|
||||
copy[i] = misc.copy(value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
local copy = {}
|
||||
for key, value in pairs(tbl) do
|
||||
copy[key] = misc.copy(value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
---Safe version of vim.str_utfindex
|
||||
---@param text string
|
||||
---@param vimindex number
|
||||
---@return number
|
||||
misc.to_utfindex = function(text, vimindex)
|
||||
return vim.str_utfindex(text, math.max(0, math.min(vimindex - 1, #text)))
|
||||
end
|
||||
|
||||
---Safe version of vim.str_byteindex
|
||||
---@param text string
|
||||
---@param utfindex number
|
||||
---@return number
|
||||
misc.to_vimindex = function(text, utfindex)
|
||||
for i = utfindex, 1, -1 do
|
||||
local s, v = pcall(function()
|
||||
return vim.str_byteindex(text, i) + 1
|
||||
end)
|
||||
if s then
|
||||
return v
|
||||
end
|
||||
end
|
||||
return utfindex + 1
|
||||
end
|
||||
|
||||
---Mark the function as deprecated
|
||||
misc.deprecated = function(fn, msg)
|
||||
local printed = false
|
||||
return function(...)
|
||||
if not printed then
|
||||
print(msg)
|
||||
printed = true
|
||||
end
|
||||
return fn(...)
|
||||
end
|
||||
end
|
||||
|
||||
return misc
|
51
bundle/nvim-cmp/lua/cmp/utils/misc_spec.lua
Normal file
51
bundle/nvim-cmp/lua/cmp/utils/misc_spec.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local spec = require('cmp.utils.spec')
|
||||
|
||||
local misc = require('cmp.utils.misc')
|
||||
|
||||
describe('misc', function()
|
||||
before_each(spec.before)
|
||||
|
||||
it('merge', function()
|
||||
local merged
|
||||
merged = misc.merge({
|
||||
a = {},
|
||||
}, {
|
||||
a = {
|
||||
b = 1,
|
||||
},
|
||||
})
|
||||
assert.are.equal(merged.a.b, 1)
|
||||
|
||||
merged = misc.merge({
|
||||
a = false,
|
||||
}, {
|
||||
a = {
|
||||
b = 1,
|
||||
},
|
||||
})
|
||||
assert.are.equal(merged.a, false)
|
||||
|
||||
merged = misc.merge({
|
||||
a = misc.none,
|
||||
}, {
|
||||
a = {
|
||||
b = 1,
|
||||
},
|
||||
})
|
||||
assert.are.equal(merged.a, nil)
|
||||
|
||||
merged = misc.merge({
|
||||
a = misc.none,
|
||||
}, {
|
||||
a = nil,
|
||||
})
|
||||
assert.are.equal(merged.a, nil)
|
||||
|
||||
merged = misc.merge({
|
||||
a = nil,
|
||||
}, {
|
||||
a = misc.none,
|
||||
})
|
||||
assert.are.equal(merged.a, nil)
|
||||
end)
|
||||
end)
|
28
bundle/nvim-cmp/lua/cmp/utils/pattern.lua
Normal file
28
bundle/nvim-cmp/lua/cmp/utils/pattern.lua
Normal file
@ -0,0 +1,28 @@
|
||||
local pattern = {}
|
||||
|
||||
pattern._regexes = {}
|
||||
|
||||
pattern.regex = function(p)
|
||||
if not pattern._regexes[p] then
|
||||
pattern._regexes[p] = vim.regex(p)
|
||||
end
|
||||
return pattern._regexes[p]
|
||||
end
|
||||
|
||||
pattern.offset = function(p, text)
|
||||
local s, e = pattern.regex(p):match_str(text)
|
||||
if s then
|
||||
return s + 1, e + 1
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
pattern.matchstr = function(p, text)
|
||||
local s, e = pattern.offset(p, text)
|
||||
if s then
|
||||
return string.sub(text, s, e)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return pattern
|
92
bundle/nvim-cmp/lua/cmp/utils/spec.lua
Normal file
92
bundle/nvim-cmp/lua/cmp/utils/spec.lua
Normal file
@ -0,0 +1,92 @@
|
||||
local context = require('cmp.context')
|
||||
local source = require('cmp.source')
|
||||
local types = require('cmp.types')
|
||||
local config = require('cmp.config')
|
||||
|
||||
local spec = {}
|
||||
|
||||
spec.before = function()
|
||||
vim.cmd([[
|
||||
bdelete!
|
||||
enew!
|
||||
imapclear
|
||||
imapclear <buffer>
|
||||
cmapclear
|
||||
cmapclear <buffer>
|
||||
smapclear
|
||||
smapclear <buffer>
|
||||
xmapclear
|
||||
xmapclear <buffer>
|
||||
tmapclear
|
||||
tmapclear <buffer>
|
||||
setlocal noswapfile
|
||||
setlocal virtualedit=all
|
||||
setlocal completeopt=menu,menuone,noselect
|
||||
]])
|
||||
config.set_global({
|
||||
sources = {
|
||||
{ name = 'spec' },
|
||||
},
|
||||
snippet = {
|
||||
expand = function(args)
|
||||
local ctx = context.new()
|
||||
vim.api.nvim_buf_set_text(ctx.bufnr, ctx.cursor.row - 1, ctx.cursor.col - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, vim.split(string.gsub(args.body, '%$0', ''), '\n'))
|
||||
for i, t in ipairs(vim.split(args.body, '\n')) do
|
||||
local s = string.find(t, '$0', 1, true)
|
||||
if s then
|
||||
if i == 1 then
|
||||
vim.api.nvim_win_set_cursor(0, { ctx.cursor.row, ctx.cursor.col + s - 2 })
|
||||
else
|
||||
vim.api.nvim_win_set_cursor(0, { ctx.cursor.row + i - 1, s - 1 })
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
})
|
||||
config.set_cmdline({
|
||||
sources = {
|
||||
{ name = 'spec' },
|
||||
},
|
||||
}, ':')
|
||||
end
|
||||
|
||||
spec.state = function(text, row, col)
|
||||
vim.fn.setline(1, text)
|
||||
vim.fn.cursor(row, col)
|
||||
local ctx = context.empty()
|
||||
local s = source.new('spec', {
|
||||
complete = function() end,
|
||||
})
|
||||
return {
|
||||
context = function()
|
||||
return ctx
|
||||
end,
|
||||
source = function()
|
||||
return s
|
||||
end,
|
||||
backspace = function()
|
||||
vim.fn.feedkeys('x', 'nx')
|
||||
vim.fn.feedkeys('h', 'nx')
|
||||
ctx = context.new(ctx, { reason = types.cmp.ContextReason.Auto })
|
||||
s:complete(ctx, function() end)
|
||||
return ctx
|
||||
end,
|
||||
input = function(char)
|
||||
vim.fn.feedkeys(('i%s'):format(char), 'nx')
|
||||
vim.fn.feedkeys(string.rep('l', #char), 'nx')
|
||||
ctx.prev_context = nil
|
||||
ctx = context.new(ctx, { reason = types.cmp.ContextReason.Auto })
|
||||
s:complete(ctx, function() end)
|
||||
return ctx
|
||||
end,
|
||||
manual = function()
|
||||
ctx = context.new(ctx, { reason = types.cmp.ContextReason.Manual })
|
||||
s:complete(ctx, function() end)
|
||||
return ctx
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return spec
|
156
bundle/nvim-cmp/lua/cmp/utils/str.lua
Normal file
156
bundle/nvim-cmp/lua/cmp/utils/str.lua
Normal file
@ -0,0 +1,156 @@
|
||||
local char = require('cmp.utils.char')
|
||||
local pattern = require('cmp.utils.pattern')
|
||||
|
||||
local str = {}
|
||||
|
||||
local INVALID_CHARS = {}
|
||||
INVALID_CHARS[string.byte("'")] = true
|
||||
INVALID_CHARS[string.byte('"')] = true
|
||||
INVALID_CHARS[string.byte('=')] = true
|
||||
INVALID_CHARS[string.byte('$')] = true
|
||||
INVALID_CHARS[string.byte('(')] = true
|
||||
INVALID_CHARS[string.byte('[')] = true
|
||||
INVALID_CHARS[string.byte(' ')] = true
|
||||
INVALID_CHARS[string.byte('\t')] = true
|
||||
INVALID_CHARS[string.byte('\n')] = true
|
||||
INVALID_CHARS[string.byte('\r')] = true
|
||||
|
||||
local NR_BYTE = string.byte('\n')
|
||||
|
||||
local PAIR_CHARS = {}
|
||||
PAIR_CHARS[string.byte('[')] = string.byte(']')
|
||||
PAIR_CHARS[string.byte('(')] = string.byte(')')
|
||||
PAIR_CHARS[string.byte('<')] = string.byte('>')
|
||||
|
||||
---Return if specified text has prefix or not
|
||||
---@param text string
|
||||
---@param prefix string
|
||||
---@return boolean
|
||||
str.has_prefix = function(text, prefix)
|
||||
if #text < #prefix then
|
||||
return false
|
||||
end
|
||||
for i = 1, #prefix do
|
||||
if not char.match(string.byte(text, i), string.byte(prefix, i)) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---Remove suffix
|
||||
---@param text string
|
||||
---@param suffix string
|
||||
---@return string
|
||||
str.remove_suffix = function(text, suffix)
|
||||
if #text < #suffix then
|
||||
return text
|
||||
end
|
||||
|
||||
local i = 0
|
||||
while i < #suffix do
|
||||
if string.byte(text, #text - i) ~= string.byte(suffix, #suffix - i) then
|
||||
return text
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return string.sub(text, 1, -#suffix - 1)
|
||||
end
|
||||
|
||||
---strikethrough
|
||||
---@param text string
|
||||
---@return string
|
||||
str.strikethrough = function(text)
|
||||
local r = pattern.regex('.')
|
||||
local buffer = ''
|
||||
while text ~= '' do
|
||||
local s, e = r:match_str(text)
|
||||
if not s then
|
||||
break
|
||||
end
|
||||
buffer = buffer .. string.sub(text, s, e) .. '̶'
|
||||
text = string.sub(text, e + 1)
|
||||
end
|
||||
return buffer
|
||||
end
|
||||
|
||||
---trim
|
||||
---@param text string
|
||||
---@return string
|
||||
str.trim = function(text)
|
||||
local s = 1
|
||||
for i = 1, #text do
|
||||
if not char.is_white(string.byte(text, i)) then
|
||||
s = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local e = #text
|
||||
for i = #text, 1, -1 do
|
||||
if not char.is_white(string.byte(text, i)) then
|
||||
e = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if s == 1 and e == #text then
|
||||
return text
|
||||
end
|
||||
return string.sub(text, s, e)
|
||||
end
|
||||
|
||||
---get_word
|
||||
---@param text string
|
||||
---@return string
|
||||
str.get_word = function(text, stop_char)
|
||||
local valids = {}
|
||||
local has_valid = false
|
||||
for idx = 1, #text do
|
||||
local c = string.byte(text, idx)
|
||||
local invalid = INVALID_CHARS[c] and not (valids[c] and stop_char ~= c)
|
||||
if has_valid and invalid then
|
||||
return string.sub(text, 1, idx - 1)
|
||||
end
|
||||
valids[c] = true
|
||||
if PAIR_CHARS[c] then
|
||||
valids[PAIR_CHARS[c]] = true
|
||||
end
|
||||
has_valid = has_valid or not invalid
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
---Oneline
|
||||
---@param text string
|
||||
---@return string
|
||||
str.oneline = function(text)
|
||||
for i = 1, #text do
|
||||
if string.byte(text, i) == NR_BYTE then
|
||||
return string.sub(text, 1, i - 1)
|
||||
end
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
---Escape special chars
|
||||
---@param text string
|
||||
---@param chars string[]
|
||||
---@return string
|
||||
str.escape = function(text, chars)
|
||||
table.insert(chars, '\\')
|
||||
local escaped = {}
|
||||
local i = 1
|
||||
while i <= #text do
|
||||
local c = string.sub(text, i, i)
|
||||
if vim.tbl_contains(chars, c) then
|
||||
table.insert(escaped, '\\')
|
||||
table.insert(escaped, c)
|
||||
else
|
||||
table.insert(escaped, c)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return table.concat(escaped, '')
|
||||
end
|
||||
|
||||
return str
|
30
bundle/nvim-cmp/lua/cmp/utils/str_spec.lua
Normal file
30
bundle/nvim-cmp/lua/cmp/utils/str_spec.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local str = require('cmp.utils.str')
|
||||
|
||||
describe('utils.str', function()
|
||||
it('get_word', function()
|
||||
assert.are.equal(str.get_word('print'), 'print')
|
||||
assert.are.equal(str.get_word('$variable'), '$variable')
|
||||
assert.are.equal(str.get_word('print()'), 'print')
|
||||
assert.are.equal(str.get_word('["cmp#confirm"]'), '["cmp#confirm"]')
|
||||
assert.are.equal(str.get_word('"devDependencies":', string.byte('"')), '"devDependencies')
|
||||
end)
|
||||
|
||||
it('strikethrough', function()
|
||||
assert.are.equal(str.strikethrough('あいうえお'), 'あ̶い̶う̶え̶お̶')
|
||||
end)
|
||||
|
||||
it('remove_suffix', function()
|
||||
assert.are.equal(str.remove_suffix('log()', '$0'), 'log()')
|
||||
assert.are.equal(str.remove_suffix('log()$0', '$0'), 'log()')
|
||||
assert.are.equal(str.remove_suffix('log()${0}', '${0}'), 'log()')
|
||||
assert.are.equal(str.remove_suffix('log()${0:placeholder}', '${0}'), 'log()${0:placeholder}')
|
||||
end)
|
||||
|
||||
it('escape', function()
|
||||
assert.are.equal(str.escape('plain', {}), 'plain')
|
||||
assert.are.equal(str.escape('plain\\', {}), 'plain\\\\')
|
||||
assert.are.equal(str.escape('plain\\"', {}), 'plain\\\\"')
|
||||
assert.are.equal(str.escape('pla"in', { '"' }), 'pla\\"in')
|
||||
assert.are.equal(str.escape('call("")', { '"' }), 'call(\\"\\")')
|
||||
end)
|
||||
end)
|
261
bundle/nvim-cmp/lua/cmp/utils/window.lua
Normal file
261
bundle/nvim-cmp/lua/cmp/utils/window.lua
Normal file
@ -0,0 +1,261 @@
|
||||
local cache = require('cmp.utils.cache')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local buffer = require('cmp.utils.buffer')
|
||||
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 zindex number|nil
|
||||
|
||||
---@class cmp.Window
|
||||
---@field public name string
|
||||
---@field public win number|nil
|
||||
---@field public swin1 number|nil
|
||||
---@field public swin2 number|nil
|
||||
---@field public style cmp.WindowStyle
|
||||
---@field public opt table<string, any>
|
||||
---@field public cache cmp.Cache
|
||||
local window = {}
|
||||
|
||||
---new
|
||||
---@return cmp.Window
|
||||
window.new = function()
|
||||
local self = setmetatable({}, { __index = window })
|
||||
self.name = misc.id('cmp.utils.window.new')
|
||||
self.win = nil
|
||||
self.swin1 = nil
|
||||
self.swin2 = nil
|
||||
self.style = {}
|
||||
self.cache = cache.new()
|
||||
self.opt = {}
|
||||
return self
|
||||
end
|
||||
|
||||
---Set window option.
|
||||
---NOTE: If the window already visible, immediately applied to it.
|
||||
---@param key string
|
||||
---@param value any
|
||||
window.option = function(self, key, value)
|
||||
if vim.fn.exists('+' .. key) == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if value == nil then
|
||||
return self.opt[key]
|
||||
end
|
||||
|
||||
self.opt[key] = value
|
||||
if self:visible() then
|
||||
vim.api.nvim_win_set_option(self.win, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
---Set style.
|
||||
---@param style cmp.WindowStyle
|
||||
window.set_style = function(self, style)
|
||||
if vim.o.columns and vim.o.columns <= style.col + style.width then
|
||||
style.width = vim.o.columns - style.col - 1
|
||||
end
|
||||
if vim.o.lines and vim.o.lines <= style.row + style.height then
|
||||
style.height = vim.o.lines - style.row - 1
|
||||
end
|
||||
self.style = style
|
||||
self.style.zindex = self.style.zindex or 1
|
||||
end
|
||||
|
||||
---Return buffer id.
|
||||
---@return number
|
||||
window.get_buffer = function(self)
|
||||
return buffer.ensure(self.name)
|
||||
end
|
||||
|
||||
---Open window
|
||||
---@param style cmp.WindowStyle
|
||||
window.open = function(self, style)
|
||||
if style then
|
||||
self:set_style(style)
|
||||
end
|
||||
|
||||
if self.style.width < 1 or self.style.height < 1 then
|
||||
return
|
||||
end
|
||||
|
||||
if self.win and vim.api.nvim_win_is_valid(self.win) then
|
||||
vim.api.nvim_win_set_config(self.win, self.style)
|
||||
else
|
||||
local s = misc.copy(self.style)
|
||||
s.noautocmd = true
|
||||
self.win = vim.api.nvim_open_win(buffer.ensure(self.name), false, s)
|
||||
for k, v in pairs(self.opt) do
|
||||
vim.api.nvim_win_set_option(self.win, k, v)
|
||||
end
|
||||
end
|
||||
self:update()
|
||||
end
|
||||
|
||||
---Update
|
||||
window.update = function(self)
|
||||
if self:has_scrollbar() then
|
||||
local total = self:get_content_height()
|
||||
local info = self:info()
|
||||
local bar_height = math.ceil(info.height * (info.height / total))
|
||||
local bar_offset = math.min(info.height - bar_height, math.floor(info.height * (vim.fn.getwininfo(self.win)[1].topline / total)))
|
||||
local style1 = {}
|
||||
style1.relative = 'editor'
|
||||
style1.style = 'minimal'
|
||||
style1.width = 1
|
||||
style1.height = info.height
|
||||
style1.row = info.row
|
||||
style1.col = info.col + info.width - (info.has_scrollbar and 1 or 0)
|
||||
style1.zindex = (self.style.zindex and (self.style.zindex + 1) or 1)
|
||||
if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
|
||||
vim.api.nvim_win_set_config(self.swin1, style1)
|
||||
else
|
||||
style1.noautocmd = true
|
||||
self.swin1 = vim.api.nvim_open_win(buffer.ensure(self.name .. 'sbuf1'), false, style1)
|
||||
vim.api.nvim_win_set_option(self.swin1, 'winhighlight', 'EndOfBuffer:PmenuSbar,Normal:PmenuSbar,NormalNC:PmenuSbar,NormalFloat:PmenuSbar')
|
||||
end
|
||||
local style2 = {}
|
||||
style2.relative = 'editor'
|
||||
style2.style = 'minimal'
|
||||
style2.width = 1
|
||||
style2.height = bar_height
|
||||
style2.row = info.row + bar_offset
|
||||
style2.col = info.col + info.width - (info.has_scrollbar and 1 or 0)
|
||||
style2.zindex = (self.style.zindex and (self.style.zindex + 2) or 2)
|
||||
if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
|
||||
vim.api.nvim_win_set_config(self.swin2, style2)
|
||||
else
|
||||
style2.noautocmd = true
|
||||
self.swin2 = vim.api.nvim_open_win(buffer.ensure(self.name .. 'sbuf2'), false, style2)
|
||||
vim.api.nvim_win_set_option(self.swin2, 'winhighlight', 'EndOfBuffer:PmenuThumb,Normal:PmenuThumb,NormalNC:PmenuThumb,NormalFloat:PmenuThumb')
|
||||
end
|
||||
else
|
||||
if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
|
||||
vim.api.nvim_win_hide(self.swin1)
|
||||
self.swin1 = nil
|
||||
end
|
||||
if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
|
||||
vim.api.nvim_win_hide(self.swin2)
|
||||
self.swin2 = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- In cmdline, vim does not redraw automatically.
|
||||
if api.is_cmdline_mode() then
|
||||
vim.api.nvim_win_call(self.win, function()
|
||||
vim.cmd([[redraw]])
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---Close window
|
||||
window.close = function(self)
|
||||
if self.win and vim.api.nvim_win_is_valid(self.win) then
|
||||
if self.win and vim.api.nvim_win_is_valid(self.win) then
|
||||
vim.api.nvim_win_hide(self.win)
|
||||
self.win = nil
|
||||
end
|
||||
if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
|
||||
vim.api.nvim_win_hide(self.swin1)
|
||||
self.swin1 = nil
|
||||
end
|
||||
if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
|
||||
vim.api.nvim_win_hide(self.swin2)
|
||||
self.swin2 = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Return the window is visible or not.
|
||||
window.visible = function(self)
|
||||
return self.win and vim.api.nvim_win_is_valid(self.win)
|
||||
end
|
||||
|
||||
---Return the scrollbar will shown or not.
|
||||
window.has_scrollbar = function(self)
|
||||
return (self.style.height or 0) < self:get_content_height()
|
||||
end
|
||||
|
||||
---Return win info.
|
||||
window.info = function(self)
|
||||
local border_width = self:get_border_width()
|
||||
local has_scrollbar = self:has_scrollbar()
|
||||
return {
|
||||
row = self.style.row,
|
||||
col = self.style.col,
|
||||
width = self.style.width + border_width + (has_scrollbar and 1 or 0),
|
||||
height = self.style.height,
|
||||
border_width = border_width,
|
||||
has_scrollbar = has_scrollbar,
|
||||
}
|
||||
end
|
||||
|
||||
---Get border width
|
||||
---@return number
|
||||
window.get_border_width = function(self)
|
||||
local border = self.style.border
|
||||
if type(border) == 'table' then
|
||||
local new_border = {}
|
||||
while #new_border < 8 do
|
||||
for _, b in ipairs(border) do
|
||||
table.insert(new_border, b)
|
||||
end
|
||||
end
|
||||
border = new_border
|
||||
end
|
||||
|
||||
local w = 0
|
||||
if border then
|
||||
if type(border) == 'string' then
|
||||
if border == 'single' then
|
||||
w = 2
|
||||
elseif border == 'solid' then
|
||||
w = 2
|
||||
elseif border == 'double' then
|
||||
w = 2
|
||||
elseif border == 'rounded' then
|
||||
w = 2
|
||||
elseif border == 'shadow' then
|
||||
w = 1
|
||||
end
|
||||
elseif type(border) == 'table' then
|
||||
local b4 = type(border[4]) == 'table' and border[4][1] or border[4]
|
||||
if #b4 > 0 then
|
||||
w = w + 1
|
||||
end
|
||||
local b8 = type(border[8]) == 'table' and border[8][1] or border[8]
|
||||
if #b8 > 0 then
|
||||
w = w + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return w
|
||||
end
|
||||
|
||||
---Get scroll height.
|
||||
---@return number
|
||||
window.get_content_height = function(self)
|
||||
if not self:option('wrap') then
|
||||
return vim.api.nvim_buf_line_count(self:get_buffer())
|
||||
end
|
||||
|
||||
return self.cache:ensure({
|
||||
'get_content_height',
|
||||
self.style.width,
|
||||
self:get_buffer(),
|
||||
vim.api.nvim_buf_get_changedtick(self:get_buffer()),
|
||||
}, function()
|
||||
local height = 0
|
||||
for _, text in ipairs(vim.api.nvim_buf_get_lines(self:get_buffer(), 0, -1, false)) do
|
||||
height = height + math.ceil(math.max(1, vim.str_utfindex(text)) / self.style.width)
|
||||
end
|
||||
return height
|
||||
end)
|
||||
end
|
||||
|
||||
return window
|
227
bundle/nvim-cmp/lua/cmp/view.lua
Normal file
227
bundle/nvim-cmp/lua/cmp/view.lua
Normal file
@ -0,0 +1,227 @@
|
||||
local config = require('cmp.config')
|
||||
local async = require('cmp.utils.async')
|
||||
local event = require('cmp.utils.event')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local docs_view = require('cmp.view.docs_view')
|
||||
local custom_entries_view = require('cmp.view.custom_entries_view')
|
||||
local native_entries_view = require('cmp.view.native_entries_view')
|
||||
local ghost_text_view = require('cmp.view.ghost_text_view')
|
||||
|
||||
---@class cmp.View
|
||||
---@field public event cmp.Event
|
||||
---@field private resolve_dedup cmp.AsyncDedup
|
||||
---@field private native_entries_view cmp.NativeEntriesView
|
||||
---@field private custom_entries_view cmp.CustomEntriesView
|
||||
---@field private change_dedup cmp.AsyncDedup
|
||||
---@field private docs_view cmp.DocsView
|
||||
---@field private ghost_text_view cmp.GhostTextView
|
||||
local view = {}
|
||||
|
||||
---Create menu
|
||||
view.new = function()
|
||||
local self = setmetatable({}, { __index = view })
|
||||
self.resolve_dedup = async.dedup()
|
||||
self.custom_entries_view = custom_entries_view.new()
|
||||
self.native_entries_view = native_entries_view.new()
|
||||
self.docs_view = docs_view.new()
|
||||
self.ghost_text_view = ghost_text_view.new()
|
||||
self.event = event.new()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---Return the view components are available or not.
|
||||
---@return boolean
|
||||
view.ready = function(self)
|
||||
return self:_get_entries_view():ready()
|
||||
end
|
||||
|
||||
---OnChange handler.
|
||||
view.on_change = function(self)
|
||||
self:_get_entries_view():on_change()
|
||||
end
|
||||
|
||||
---Open menu
|
||||
---@param ctx cmp.Context
|
||||
---@param sources cmp.Source[]
|
||||
view.open = function(self, ctx, sources)
|
||||
local source_group_map = {}
|
||||
for _, s in ipairs(sources) do
|
||||
local group_index = s:get_config().group_index or 0
|
||||
if not source_group_map[group_index] then
|
||||
source_group_map[group_index] = {}
|
||||
end
|
||||
table.insert(source_group_map[group_index], s)
|
||||
end
|
||||
|
||||
local group_indexes = vim.tbl_keys(source_group_map)
|
||||
table.sort(group_indexes, function(a, b)
|
||||
return a ~= b and (a < b) or nil
|
||||
end)
|
||||
|
||||
local entries = {}
|
||||
for _, group_index in ipairs(group_indexes) do
|
||||
local source_group = source_group_map[group_index] or {}
|
||||
|
||||
-- check the source triggered by character
|
||||
local has_triggered_by_symbol_source = false
|
||||
for _, s in ipairs(source_group) do
|
||||
if #s:get_entries(ctx) > 0 then
|
||||
if s.is_triggered_by_symbol then
|
||||
has_triggered_by_symbol_source = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- create filtered entries.
|
||||
local offset = ctx.cursor.col
|
||||
for i, s in ipairs(source_group) do
|
||||
if s.offset <= offset then
|
||||
if not has_triggered_by_symbol_source or s.is_triggered_by_symbol then
|
||||
-- source order priority bonus.
|
||||
local priority = s:get_config().priority or ((#source_group - (i - 1)) * config.get().sorting.priority_weight)
|
||||
|
||||
for _, e in ipairs(s:get_entries(ctx)) do
|
||||
e.score = e.score + priority
|
||||
table.insert(entries, e)
|
||||
offset = math.min(offset, e:get_offset())
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- sort.
|
||||
local comparetors = config.get().sorting.comparators
|
||||
table.sort(entries, function(e1, e2)
|
||||
for _, fn in ipairs(comparetors) do
|
||||
local diff = fn(e1, e2)
|
||||
if diff ~= nil then
|
||||
return diff
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- open
|
||||
if #entries > 0 then
|
||||
self:_get_entries_view():open(offset, entries)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- close.
|
||||
if #entries == 0 then
|
||||
self:close()
|
||||
end
|
||||
end
|
||||
|
||||
---Close menu
|
||||
view.close = function(self)
|
||||
self:_get_entries_view():close()
|
||||
self.docs_view:close()
|
||||
self.ghost_text_view:hide()
|
||||
end
|
||||
|
||||
---Abort menu
|
||||
view.abort = function(self)
|
||||
self:_get_entries_view():abort()
|
||||
self.docs_view:close()
|
||||
self.ghost_text_view:hide()
|
||||
end
|
||||
|
||||
---Return the view is visible or not.
|
||||
---@return boolean
|
||||
view.visible = function(self)
|
||||
return self:_get_entries_view():visible()
|
||||
end
|
||||
|
||||
---Scroll documentation window if possible.
|
||||
---@param delta number
|
||||
view.scroll_docs = function(self, delta)
|
||||
self.docs_view:scroll(delta)
|
||||
end
|
||||
|
||||
---Select prev menu item.
|
||||
---@param option cmp.SelectOption
|
||||
view.select_next_item = function(self, option)
|
||||
self:_get_entries_view():select_next_item(option)
|
||||
end
|
||||
|
||||
---Select prev menu item.
|
||||
---@param option cmp.SelectOption
|
||||
view.select_prev_item = function(self, option)
|
||||
self:_get_entries_view():select_prev_item(option)
|
||||
end
|
||||
|
||||
---Get first entry
|
||||
---@param self cmp.Entry|nil
|
||||
view.get_first_entry = function(self)
|
||||
return self:_get_entries_view():get_first_entry()
|
||||
end
|
||||
|
||||
---Get current selected entry
|
||||
---@return cmp.Entry|nil
|
||||
view.get_selected_entry = function(self)
|
||||
return self:_get_entries_view():get_selected_entry()
|
||||
end
|
||||
|
||||
---Get current active entry
|
||||
---@return cmp.Entry|nil
|
||||
view.get_active_entry = function(self)
|
||||
return self:_get_entries_view():get_active_entry()
|
||||
end
|
||||
|
||||
---Return current configured entries_view
|
||||
---@return cmp.CustomEntriesView|cmp.NativeEntriesView
|
||||
view._get_entries_view = function(self)
|
||||
local c = config.get()
|
||||
self.native_entries_view.event:clear()
|
||||
self.custom_entries_view.event:clear()
|
||||
|
||||
if c.experimental.native_menu then
|
||||
self.native_entries_view.event:on('change', function()
|
||||
self:on_entry_change()
|
||||
end)
|
||||
return self.native_entries_view
|
||||
else
|
||||
self.custom_entries_view.event:on('change', function()
|
||||
self:on_entry_change()
|
||||
end)
|
||||
return self.custom_entries_view
|
||||
end
|
||||
end
|
||||
|
||||
---On entry change
|
||||
view.on_entry_change = async.throttle(
|
||||
vim.schedule_wrap(function(self)
|
||||
if not self:visible() then
|
||||
return
|
||||
end
|
||||
local e = self:get_selected_entry()
|
||||
if e then
|
||||
for _, c in ipairs(config.get().confirmation.get_commit_characters(e:get_commit_characters())) do
|
||||
keymap.listen('i', c, function(...)
|
||||
self.event:emit('keymap', ...)
|
||||
end)
|
||||
end
|
||||
e:resolve(vim.schedule_wrap(self.resolve_dedup(function()
|
||||
if not self:visible() then
|
||||
return
|
||||
end
|
||||
self.docs_view:open(e, self:_get_entries_view():info())
|
||||
end)))
|
||||
else
|
||||
self.docs_view:close()
|
||||
end
|
||||
|
||||
e = e or self:get_first_entry()
|
||||
if e then
|
||||
self.ghost_text_view:show(e)
|
||||
else
|
||||
self.ghost_text_view:hide()
|
||||
end
|
||||
end),
|
||||
20
|
||||
)
|
||||
|
||||
return view
|
53
bundle/nvim-cmp/lua/cmp/vim_source.lua
Normal file
53
bundle/nvim-cmp/lua/cmp/vim_source.lua
Normal file
@ -0,0 +1,53 @@
|
||||
local misc = require('cmp.utils.misc')
|
||||
|
||||
local vim_source = {}
|
||||
|
||||
---@param id number
|
||||
---@param args any[]
|
||||
vim_source.on_callback = function(id, args)
|
||||
if vim_source.to_callback.callbacks[id] then
|
||||
vim_source.to_callback.callbacks[id](unpack(args))
|
||||
end
|
||||
end
|
||||
|
||||
---@param callback function
|
||||
---@return number
|
||||
vim_source.to_callback = setmetatable({
|
||||
callbacks = {},
|
||||
}, {
|
||||
__call = function(self, callback)
|
||||
local id = misc.id('cmp.vim_source.to_callback')
|
||||
self.callbacks[id] = function(...)
|
||||
callback(...)
|
||||
self.callbacks[id] = nil
|
||||
end
|
||||
return id
|
||||
end,
|
||||
})
|
||||
|
||||
---Convert to serializable args.
|
||||
---@param args any[]
|
||||
vim_source.to_args = function(args)
|
||||
for i, arg in ipairs(args) do
|
||||
if type(arg) == 'function' then
|
||||
args[i] = vim_source.to_callback(arg)
|
||||
end
|
||||
end
|
||||
return args
|
||||
end
|
||||
|
||||
---@param bridge_id number
|
||||
---@param methods string[]
|
||||
vim_source.new = function(bridge_id, methods)
|
||||
local self = {}
|
||||
for _, method in ipairs(methods) do
|
||||
self[method] = (function(m)
|
||||
return function(_, ...)
|
||||
return vim.fn['cmp#_method'](bridge_id, m, vim_source.to_args({ ... }))
|
||||
end
|
||||
end)(method)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
return vim_source
|
127
bundle/nvim-cmp/plugin/cmp.lua
Normal file
127
bundle/nvim-cmp/plugin/cmp.lua
Normal file
@ -0,0 +1,127 @@
|
||||
if vim.g.loaded_cmp then
|
||||
return
|
||||
end
|
||||
vim.g.loaded_cmp = true
|
||||
|
||||
local api = require "cmp.utils.api"
|
||||
local misc = require('cmp.utils.misc')
|
||||
local config = require('cmp.config')
|
||||
local highlight = require('cmp.utils.highlight')
|
||||
|
||||
-- TODO: https://github.com/neovim/neovim/pull/14661
|
||||
vim.cmd [[
|
||||
augroup ___cmp___
|
||||
autocmd!
|
||||
autocmd InsertEnter * lua require'cmp.utils.autocmd'.emit('InsertEnter')
|
||||
autocmd InsertLeave * lua require'cmp.utils.autocmd'.emit('InsertLeave')
|
||||
autocmd TextChangedI,TextChangedP * lua require'cmp.utils.autocmd'.emit('TextChanged')
|
||||
autocmd CursorMovedI * lua require'cmp.utils.autocmd'.emit('CursorMoved')
|
||||
autocmd CompleteChanged * lua require'cmp.utils.autocmd'.emit('CompleteChanged')
|
||||
autocmd CompleteDone * lua require'cmp.utils.autocmd'.emit('CompleteDone')
|
||||
autocmd ColorScheme * call v:lua.cmp.plugin.colorscheme()
|
||||
autocmd CmdlineEnter * call v:lua.cmp.plugin.cmdline.enter()
|
||||
augroup END
|
||||
]]
|
||||
|
||||
misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'enter' }, function()
|
||||
if config.get().experimental.native_menu then
|
||||
return
|
||||
end
|
||||
if vim.fn.expand('<afile>')~= '=' then
|
||||
vim.schedule(function()
|
||||
if api.is_cmdline_mode() then
|
||||
vim.cmd [[
|
||||
augroup cmp-cmdline
|
||||
autocmd!
|
||||
autocmd CmdlineChanged * lua require'cmp.utils.autocmd'.emit('TextChanged')
|
||||
autocmd CmdlineLeave * call v:lua.cmp.plugin.cmdline.leave()
|
||||
augroup END
|
||||
]]
|
||||
require('cmp.utils.autocmd').emit('CmdlineEnter')
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'leave' }, function()
|
||||
if vim.fn.expand('<afile>') ~= '=' then
|
||||
vim.cmd [[
|
||||
augroup cmp-cmdline
|
||||
autocmd!
|
||||
augroup END
|
||||
]]
|
||||
require('cmp.utils.autocmd').emit('CmdlineLeave')
|
||||
end
|
||||
end)
|
||||
|
||||
misc.set(_G, { 'cmp', 'plugin', 'colorscheme' }, function()
|
||||
highlight.inherit('CmpItemAbbrDefault', 'Pmenu', {
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
})
|
||||
highlight.inherit('CmpItemAbbrDeprecatedDefault', 'Comment', {
|
||||
gui = 'NONE',
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
})
|
||||
highlight.inherit('CmpItemAbbrMatchDefault', 'Pmenu', {
|
||||
gui = 'NONE',
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
})
|
||||
highlight.inherit('CmpItemAbbrMatchFuzzyDefault', 'Pmenu', {
|
||||
gui = 'NONE',
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
})
|
||||
highlight.inherit('CmpItemKindDefault', 'Special', {
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
})
|
||||
highlight.inherit('CmpItemMenuDefault', 'Pmenu', {
|
||||
guibg = 'NONE',
|
||||
ctermbg = 'NONE',
|
||||
})
|
||||
end)
|
||||
_G.cmp.plugin.colorscheme()
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbr') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbr CmpItemAbbrDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbrDeprecated') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbrDeprecated CmpItemAbbrDeprecatedDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbrMatch') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbrMatch CmpItemAbbrMatchDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemAbbrMatchFuzzy') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemAbbrMatchFuzzy CmpItemAbbrMatchFuzzyDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemKind') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemKind CmpItemKindDefault]]
|
||||
end
|
||||
|
||||
if vim.fn.hlexists('CmpItemMenu') ~= 1 then
|
||||
vim.cmd [[highlight! default link CmpItemMenu CmpItemMenuDefault]]
|
||||
end
|
||||
|
||||
vim.cmd [[command! CmpStatus lua require('cmp').status()]]
|
||||
|
||||
vim.cmd [[doautocmd <nomodeline> User cmp#ready]]
|
||||
|
||||
if vim.on_key then
|
||||
vim.on_key(function(keys)
|
||||
if keys == vim.api.nvim_replace_termcodes('<C-c>', true, true, true) then
|
||||
vim.schedule(function()
|
||||
if not api.is_suitable_mode() then
|
||||
require('cmp.utils.autocmd').emit('InsertLeave')
|
||||
end
|
||||
end)
|
||||
end
|
||||
end, vim.api.nvim_create_namespace('cmp.plugin'))
|
||||
end
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user