* 3.17

* PositionEncodingKind

* Implement PositionEncodingKind

* Remove luarc.json
This commit is contained in:
hrsh7th
2022-11-16 01:27:15 +09:00
committed by GitHub
parent aee40113c2
commit e820335208
9 changed files with 353 additions and 94 deletions

View File

@@ -6,7 +6,16 @@ let s:sources = {}
" "
function! cmp#register_source(name, source) abort function! cmp#register_source(name, source) abort
let l:methods = [] let l:methods = []
for l:method in ['is_available', 'get_debug_name', 'get_trigger_characters', 'get_keyword_pattern', 'complete', 'execute', 'resolve'] for l:method in [
\ 'is_available',
\ 'get_debug_name',
\ 'get_position_encoding_kind',
\ 'get_trigger_characters',
\ 'get_keyword_pattern',
\ 'complete',
\ 'execute',
\ 'resolve'
\ ]
if has_key(a:source, l:method) && type(a:source[l:method]) == v:t_func if has_key(a:source, l:method) && type(a:source[l:method]) == v:t_func
call add(l:methods, l:method) call add(l:methods, l:method)
endif endif
@@ -39,6 +48,8 @@ function! cmp#_method(bridge_id, method, args) abort
return l:source[a:method]() return l:source[a:method]()
elseif a:method ==# 'get_debug_name' elseif a:method ==# 'get_debug_name'
return l:source[a:method]() return l:source[a:method]()
elseif a:method ==# 'get_position_encoding_kind'
return l:source[a:method](a:args[0])
elseif a:method ==# 'get_keyword_pattern' elseif a:method ==# 'get_keyword_pattern'
return l:source[a:method](a:args[0]) return l:source[a:method](a:args[0])
elseif a:method ==# 'get_trigger_characters' elseif a:method ==# 'get_trigger_characters'

View File

@@ -735,6 +735,13 @@ Here is an example on how to create a custom source:
return 'debug name' return 'debug name'
end end
---Return LSP's PositionEncodingKind.
---@NOTE: If this method is ommited, the default value will be `utf-16`.
---@return lsp.PositionEncodingKind
function source:get_position_encoding_kind()
return 'utf-16'
end
---Return the keyword pattern for triggering completion (optional). ---Return the keyword pattern for triggering completion (optional).
---If this is ommited, nvim-cmp will use a default keyword pattern. See |cmp-config.completion.keyword_pattern|. ---If this is ommited, nvim-cmp will use a default keyword pattern. See |cmp-config.completion.keyword_pattern|.
---@return string ---@return string

View File

@@ -344,7 +344,10 @@ end, config.get().performance.throttle)
---@param callback function ---@param callback function
core.confirm = function(self, e, option, callback) core.confirm = function(self, e, option, callback)
if not (e and not e.confirmed) then if not (e and not e.confirmed) then
return callback() if callback then
callback()
end
return
end end
e.confirmed = true e.confirmed = true
@@ -357,9 +360,10 @@ core.confirm = function(self, e, option, callback)
feedkeys.call(keymap.indentkeys(), 'n') feedkeys.call(keymap.indentkeys(), 'n')
feedkeys.call('', 'n', function() feedkeys.call('', 'n', function()
-- Emulate `<C-y>` behavior to save `.` register.
local ctx = context.new() local ctx = context.new()
local keys = {} local keys = {}
table.insert(keys, keymap.backspace(ctx.cursor.character - misc.to_utfindex(ctx.cursor_line, e:get_offset()))) table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
table.insert(keys, e:get_word()) table.insert(keys, e:get_word())
table.insert(keys, keymap.undobreak()) table.insert(keys, keymap.undobreak())
feedkeys.call(table.concat(keys, ''), 'in') feedkeys.call(table.concat(keys, ''), 'in')
@@ -368,7 +372,7 @@ core.confirm = function(self, e, option, callback)
local ctx = context.new() local ctx = context.new()
if api.is_cmdline_mode() then if api.is_cmdline_mode() then
local keys = {} local keys = {}
table.insert(keys, keymap.backspace(ctx.cursor.character - misc.to_utfindex(ctx.cursor_line, e:get_offset()))) table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset())) table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset()))
feedkeys.call(table.concat(keys, ''), 'in') feedkeys.call(table.concat(keys, ''), 'in')
else else
@@ -405,11 +409,15 @@ core.confirm = function(self, e, option, callback)
return return
end end
vim.cmd([[silent! undojoin]]) vim.cmd([[silent! undojoin]])
vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, 'utf-16') vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, e.source:get_position_encoding_kind())
end) end)
else else
vim.cmd([[silent! undojoin]]) vim.cmd([[silent! undojoin]])
vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, 'utf-16') vim.lsp.util.apply_text_edits(
e:get_completion_item().additionalTextEdits,
ctx.bufnr,
e.source:get_position_encoding_kind()
)
end end
end) end)
feedkeys.call('', 'n', function() feedkeys.call('', 'n', function()
@@ -426,30 +434,33 @@ core.confirm = function(self, e, option, callback)
completion_item.textEdit.range = e:get_insert_range() completion_item.textEdit.range = e:get_insert_range()
end end
local diff_before = math.max(0, e.context.cursor.character - completion_item.textEdit.range.start.character) local diff_before = math.max(0, e.context.cursor.col - (completion_item.textEdit.range.start.character + 1))
local diff_after = math.max(0, completion_item.textEdit.range['end'].character - e.context.cursor.character) local diff_after = math.max(0, (completion_item.textEdit.range['end'].character + 1) - e.context.cursor.col)
local new_text = completion_item.textEdit.newText local new_text = completion_item.textEdit.newText
if api.is_insert_mode() then if api.is_insert_mode() then
local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet
completion_item.textEdit.range.start.line = ctx.cursor.line completion_item.textEdit.range.start.line = ctx.cursor.line
completion_item.textEdit.range.start.character = ctx.cursor.character - diff_before completion_item.textEdit.range.start.character = (e.context.cursor.col - 1) - diff_before
completion_item.textEdit.range['end'].line = ctx.cursor.line completion_item.textEdit.range['end'].line = ctx.cursor.line
completion_item.textEdit.range['end'].character = ctx.cursor.character + diff_after completion_item.textEdit.range['end'].character = (ctx.cursor.col - 1) + diff_after
local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet
if is_snippet then if is_snippet then
completion_item.textEdit.newText = '' completion_item.textEdit.newText = ''
end end
vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-16') vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-8')
local texts = vim.split(completion_item.textEdit.newText, '\n') local texts = vim.split(completion_item.textEdit.newText, '\n')
local position = completion_item.textEdit.range.start vim.api.nvim_win_set_cursor(0, {
position.line = position.line + (#texts - 1) completion_item.textEdit.range.start.line + #texts,
if #texts == 1 then (
position.character = position.character + misc.to_utfindex(texts[1]) #texts == 1 and (
else completion_item.textEdit.range.start.character + #texts[1]
position.character = misc.to_utfindex(texts[#texts]) ) or (
end #texts[#texts]
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 if is_snippet then
config.get().snippet.expand({ config.get().snippet.expand({
body = new_text, body = new_text,
@@ -458,8 +469,8 @@ core.confirm = function(self, e, option, callback)
end end
else else
local keys = {} local keys = {}
table.insert(keys, string.rep(keymap.t('<BS>'), diff_before)) table.insert(keys, keymap.backspace(ctx.cursor_line:sub(completion_item.textEdit.range.start.character + 1, ctx.cursor.col - 1)))
table.insert(keys, string.rep(keymap.t('<Del>'), diff_after)) table.insert(keys, keymap.delete(ctx.cursor_line:sub(ctx.cursor.col, completion_item.textEdit.range['end'].character)))
table.insert(keys, new_text) table.insert(keys, new_text)
feedkeys.call(table.concat(keys, ''), 'in') feedkeys.call(table.concat(keys, ''), 'in')
end end

View File

@@ -8,9 +8,19 @@ local api = require('cmp.utils.api')
describe('cmp.core', function() describe('cmp.core', function()
describe('confirm', function() describe('confirm', function()
local confirm = function(request, filter, completion_item) ---@param request string
---@param filter string
---@param completion_item lsp.CompletionItem
---@param option? { position_encoding_kind: lsp.PositionEncodingKind }
---@return table
local confirm = function(request, filter, completion_item, option)
option = option or {}
local c = core.new() local c = core.new()
local s = source.new('spec', { local s = source.new('spec', {
get_position_encoding_kind = function()
return option.position_encoding_kind or types.lsp.PositionEncodingKind.UTF16
end,
complete = function(_, _, callback) complete = function(_, _, callback)
callback({ completion_item }) callback({ completion_item })
end, end,
@@ -23,7 +33,8 @@ describe('cmp.core', function()
end) end)
end) end)
feedkeys.call(filter, 'n', function() feedkeys.call(filter, 'n', function()
c:confirm(c.sources[s.id].entries[1], {}) c:confirm(c.sources[s.id].entries[1], {}, function()
end)
end) end)
local state = {} local state = {}
feedkeys.call('', 'x', function() feedkeys.call('', 'x', function()
@@ -111,6 +122,46 @@ describe('cmp.core', function()
assert.are.same(state.buffer, { '***foo', 'bar', 'baz***' }) assert.are.same(state.buffer, { '***foo', 'bar', 'baz***' })
assert.are.same(state.cursor, { 2, 2 }) assert.are.same(state.cursor, { 2, 2 })
end) end)
local char = '🗿'
for _, case in ipairs({
{
encoding = types.lsp.PositionEncodingKind.UTF8,
char_size = #char
},
{
encoding = types.lsp.PositionEncodingKind.UTF16,
char_size = select(2, vim.str_utfindex(char))
},
{
encoding = types.lsp.PositionEncodingKind.UTF32,
char_size = select(1, vim.str_utfindex(char))
},
}) do
it('textEdit & multibyte: ' .. case.encoding , function()
local state = confirm(keymap.t('i%s:%s%s:%s<Left><Left><Left>'):format(char, char, char, char), char, {
label = char .. char .. char,
textEdit = {
range = {
start = {
line = 0,
character = case.char_size + #':'
},
['end'] = {
line = 0,
character = case.char_size + #':' + case.char_size + case.char_size
},
},
newText = char .. char .. char .. char .. char,
}
}, {
position_encoding_kind = case.encoding
})
vim.pretty_print({ state = state, case = case })
assert.are.same(state.buffer, { ('%s:%s%s%s%s%s:%s'):format(char, char, char, char, char, char, char) })
assert.are.same(state.cursor, { 1, #('%s:%s%s%s%s%s'):format(char, char, char, char, char, char) })
end)
end
end) end)
describe('cmdline-mode', function() describe('cmdline-mode', function()

View File

@@ -29,8 +29,9 @@ local entry = {}
---@param ctx cmp.Context ---@param ctx cmp.Context
---@param source cmp.Source ---@param source cmp.Source
---@param completion_item lsp.CompletionItem ---@param completion_item lsp.CompletionItem
---@param item_defaults? lsp.internal.CompletionItemDefaults
---@return cmp.Entry ---@return cmp.Entry
entry.new = function(ctx, source, completion_item) entry.new = function(ctx, source, completion_item, item_defaults)
local self = setmetatable({}, { __index = entry }) local self = setmetatable({}, { __index = entry })
self.id = misc.id('entry.new') self.id = misc.id('entry.new')
self.cache = cache.new() self.cache = cache.new()
@@ -43,7 +44,7 @@ entry.new = function(ctx, source, completion_item)
self.source_offset = source.request_offset self.source_offset = source.request_offset
self.source_insert_range = source:get_default_insert_range() self.source_insert_range = source:get_default_insert_range()
self.source_replace_range = source:get_default_replace_range() self.source_replace_range = source:get_default_replace_range()
self.completion_item = completion_item self.completion_item = self:fill_defaults(completion_item, item_defaults)
self.resolved_completion_item = nil self.resolved_completion_item = nil
self.resolved_callbacks = {} self.resolved_callbacks = {}
self.resolving = false self.resolving = false
@@ -57,11 +58,10 @@ entry.get_offset = function(self)
return self.cache:ensure('get_offset', function() return self.cache:ensure('get_offset', function()
local offset = self.source_offset local offset = self.source_offset
if misc.safe(self:get_completion_item().textEdit) then if misc.safe(self:get_completion_item().textEdit) then
local range = misc.safe(self:get_completion_item().textEdit.insert) or misc.safe(self:get_completion_item().textEdit.range) local range = self:get_insert_range()
if range then if range then
offset = self.context.cache:ensure({ 'entry', 'get_offset', range.start.character }, function() offset = self.context.cache:ensure({ 'entry', 'get_offset', tostring(range.start.character) }, function()
local c = misc.to_vimindex(self.context.cursor_line, range.start.character) for idx = range.start.character + 1, self.source_offset do
for idx = c, self.source_offset do
if not char.is_white(string.byte(self.context.cursor_line, idx)) then if not char.is_white(string.byte(self.context.cursor_line, idx)) then
return idx return idx
end end
@@ -128,7 +128,7 @@ entry.get_word = function(self)
word = str.trim(self:get_completion_item().label) word = str.trim(self:get_completion_item().label)
end end
return str.oneline(word) return str.oneline(word)
end) end) --[[@as string]]
end end
---Get overwrite information ---Get overwrite information
@@ -136,13 +136,15 @@ end
entry.get_overwrite = function(self) entry.get_overwrite = function(self)
return self.cache:ensure('get_overwrite', function() return self.cache:ensure('get_overwrite', function()
if misc.safe(self:get_completion_item().textEdit) then if misc.safe(self:get_completion_item().textEdit) then
local range = misc.safe(self:get_completion_item().textEdit.insert) or misc.safe(self:get_completion_item().textEdit.range) local range = self:get_insert_range()
if range then if range then
return self.context.cache:ensure({ 'entry', 'get_overwrite', range.start.character, range['end'].character }, function() return self.context.cache:ensure({ 'entry', 'get_overwrite', tostring(range.start.character),
local s = misc.to_vimindex(self.context.cursor_line, range.start.character) tostring(range['end'].character) },
local e = misc.to_vimindex(self.context.cursor_line, range['end'].character) function()
local before = self.context.cursor.col - s local vim_start = range.start.character + 1
local after = e - self.context.cursor.col local vim_end = range['end'].character + 1
local before = self.context.cursor.col - vim_start
local after = vim_end - self.context.cursor.col
return { before, after } return { before, after }
end) end)
end end
@@ -190,7 +192,8 @@ end
---Return the item is deprecated or not. ---Return the item is deprecated or not.
---@return boolean ---@return boolean
entry.is_deprecated = function(self) entry.is_deprecated = function(self)
return self:get_completion_item().deprecated or vim.tbl_contains(self:get_completion_item().tags or {}, types.lsp.CompletionItemTag.Deprecated) return self:get_completion_item().deprecated or
vim.tbl_contains(self:get_completion_item().tags or {}, types.lsp.CompletionItemTag.Deprecated)
end end
---Return view information. ---Return view information.
@@ -199,7 +202,7 @@ end
---@return { abbr: { text: string, bytes: integer, width: integer, hl_group: string }, kind: { text: string, bytes: integer, width: integer, hl_group: string }, menu: { text: string, bytes: integer, width: integer, hl_group: string } } ---@return { abbr: { text: string, bytes: integer, width: integer, hl_group: string }, kind: { text: string, bytes: integer, width: integer, hl_group: string }, menu: { text: string, bytes: integer, width: integer, hl_group: string } }
entry.get_view = function(self, suggest_offset, entries_buf) entry.get_view = function(self, suggest_offset, entries_buf)
local item = self:get_vim_item(suggest_offset) local item = self:get_vim_item(suggest_offset)
return self.cache:ensure({ 'get_view', entries_buf }, function() return self.cache:ensure({ 'get_view', tostring(entries_buf) }, function()
local view = {} local view = {}
-- The result of vim.fn.strdisplaywidth depends on which buffer it was -- The result of vim.fn.strdisplaywidth depends on which buffer it was
-- called in because it reads the values of the option 'tabstop' when -- called in because it reads the values of the option 'tabstop' when
@@ -214,7 +217,9 @@ entry.get_view = function(self, suggest_offset, entries_buf)
view.kind.text = item.kind or '' view.kind.text = item.kind or ''
view.kind.bytes = #view.kind.text view.kind.bytes = #view.kind.text
view.kind.width = vim.fn.strdisplaywidth(view.kind.text) view.kind.width = vim.fn.strdisplaywidth(view.kind.text)
view.kind.hl_group = item.kind_hl_group or ('CmpItemKind' .. (types.lsp.CompletionItemKind[self:get_kind()] or '')) view.kind.hl_group = item.kind_hl_group or
('CmpItemKind' .. (types.lsp.CompletionItemKind[self:get_kind()] or '')
)
view.menu = {} view.menu = {}
view.menu.text = item.menu or '' view.menu.text = item.menu or ''
view.menu.bytes = #view.menu.text view.menu.bytes = #view.menu.text
@@ -230,7 +235,7 @@ end
---@param suggest_offset integer ---@param suggest_offset integer
---@return vim.CompletedItem ---@return vim.CompletedItem
entry.get_vim_item = function(self, suggest_offset) entry.get_vim_item = function(self, suggest_offset)
return self.cache:ensure({ 'get_vim_item', suggest_offset }, function() return self.cache:ensure({ 'get_vim_item', tostring(suggest_offset) }, function()
local completion_item = self:get_completion_item() local completion_item = self:get_completion_item()
local word = self:get_word() local word = self:get_word()
local abbr = str.oneline(completion_item.label) local abbr = str.oneline(completion_item.label)
@@ -314,13 +319,17 @@ entry.get_insert_range = function(self)
if misc.safe(self:get_completion_item().textEdit.insert) then if misc.safe(self:get_completion_item().textEdit.insert) then
insert_range = self:get_completion_item().textEdit.insert insert_range = self:get_completion_item().textEdit.insert
else else
insert_range = self:get_completion_item().textEdit.range insert_range = self:get_completion_item().textEdit.range --[[@as lsp.Range]]
end end
insert_range = {
start = self:convert_position_encoding(insert_range.start),
['end'] = self:convert_position_encoding(insert_range['end']),
}
else else
insert_range = { insert_range = {
start = { start = {
line = self.context.cursor.row - 1, line = self.context.cursor.row - 1,
character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), self.source_insert_range.start.character), character = self:get_offset() - 1,
}, },
['end'] = self.source_insert_range['end'], ['end'] = self.source_insert_range['end'],
} }
@@ -337,15 +346,19 @@ entry.get_replace_range = function(self)
if misc.safe(self:get_completion_item().textEdit.replace) then if misc.safe(self:get_completion_item().textEdit.replace) then
replace_range = self:get_completion_item().textEdit.replace replace_range = self:get_completion_item().textEdit.replace
else else
replace_range = self:get_completion_item().textEdit.range replace_range = self:get_completion_item().textEdit.range --[[@as lsp.Range]]
end end
replace_range = {
start = self:convert_position_encoding(replace_range.start),
['end'] = self:convert_position_encoding(replace_range['end']),
}
end end
if not replace_range or (self.context.cursor.character == replace_range['end'].character) then if not replace_range or ((self.context.cursor.col - 1) == replace_range['end'].character) then
replace_range = { replace_range = {
start = { start = {
line = self.source_replace_range.start.line, line = self.source_replace_range.start.line,
character = math.min(misc.to_utfindex(self.context.cursor_line, self:get_offset()), self.source_replace_range.start.character), character = self:get_offset() - 1
}, },
['end'] = self.source_replace_range['end'], ['end'] = self.source_replace_range['end'],
} }
@@ -361,10 +374,10 @@ end
entry.match = function(self, input, matching_config) entry.match = function(self, input, matching_config)
return self.match_cache:ensure({ return self.match_cache:ensure({
input, input,
self.resolved_completion_item and 1 or 0, self.resolved_completion_item and '1' or '0',
matching_config.disallow_fuzzy_matching and 1 or 0, matching_config.disallow_fuzzy_matching and '1' or '0',
matching_config.disallow_partial_matching and 1 or 0, matching_config.disallow_partial_matching and '1' or '0',
matching_config.disallow_prefix_unmatching and 1 or 0, matching_config.disallow_prefix_unmatching and '1' or '0',
}, function() }, function()
local option = { local option = {
disallow_fuzzy_matching = matching_config.disallow_fuzzy_matching, disallow_fuzzy_matching = matching_config.disallow_fuzzy_matching,
@@ -492,4 +505,60 @@ entry.resolve = function(self, callback)
end end
end end
---@param completion_item lsp.CompletionItem
---@param defaults? lsp.internal.CompletionItemDefaults
---@return lsp.CompletionItem
entry.fill_defaults = function(_, completion_item, defaults)
defaults = defaults or {}
if defaults.data then
completion_item.data = completion_item.data or defaults.data
end
if defaults.commitCharacters then
completion_item.commitCharacters = completion_item.commitCharacters or defaults.commitCharacters
end
if defaults.insertTextFormat then
completion_item.insertTextFormat = completion_item.insertTextFormat or defaults.insertTextFormat
end
if defaults.insertTextMode then
completion_item.insertTextMode = completion_item.insertTextMode or defaults.insertTextMode
end
if defaults.editRange then
if not completion_item.textEdit then
if defaults.editRange.insert then
completion_item.textEdit = {
insert = defaults.editRange.insert,
replace = defaults.editRange.replace,
newText = completion_item.textEditText or completion_item.label,
}
else
completion_item.textEdit = {
range = defaults.editRange --[[@as lsp.Range]] ,
newText = completion_item.textEditText or completion_item.label,
}
end
end
end
return completion_item
end
---Convert the oneline range encoding.
entry.convert_position_encoding = function(self, position)
local from_encoding = self.source:get_position_encoding_kind()
return self.context.cache:ensure('entry.convert_position_encoding.' .. position.character .. '.' .. from_encoding,
function()
return types.lsp.Position.to_utf8(
self.context.cursor_line,
position,
from_encoding
)
end
)
end
return entry return entry

View File

@@ -46,7 +46,6 @@ source.new = function(name, s)
end end
---Reset current completion state ---Reset current completion state
---@return boolean
source.reset = function(self) source.reset = function(self)
self.cache:clear() self.cache:clear()
self.revision = self.revision + 1 self.revision = self.revision + 1
@@ -124,7 +123,7 @@ source.get_entries = function(self, ctx)
end end
end end
end end
self.cache:set({ 'get_entries', self.revision, ctx.cursor_before_line }, entries) self.cache:set({ 'get_entries', tostring(self.revision), ctx.cursor_before_line }, entries)
local max_item_count = self:get_source_config().max_item_count or 200 local max_item_count = self:get_source_config().max_item_count or 200
local limited_entries = {} local limited_entries = {}
@@ -137,44 +136,44 @@ source.get_entries = function(self, ctx)
return limited_entries return limited_entries
end end
---Get default insert range ---Get default insert range (UTF8 byte index).
---@return lsp.Range ---@return lsp.Range
source.get_default_insert_range = function(self) source.get_default_insert_range = function(self)
if not self.context then if not self.context then
error('context is not initialized yet.') error('context is not initialized yet.')
end end
return self.cache:ensure({ 'get_default_insert_range', self.revision }, function() return self.cache:ensure({ 'get_default_insert_range', tostring(self.revision) }, function()
return { return {
start = { start = {
line = self.context.cursor.row - 1, line = self.context.cursor.row - 1,
character = misc.to_utfindex(self.context.cursor_line, self.offset), character = self.offset - 1,
}, },
['end'] = { ['end'] = {
line = self.context.cursor.row - 1, line = self.context.cursor.row - 1,
character = misc.to_utfindex(self.context.cursor_line, self.context.cursor.col), character = self.context.cursor.col - 1,
}, },
} }
end) end)
end end
---Get default replace range ---Get default replace range (UTF8 byte index).
---@return lsp.Range ---@return lsp.Range
source.get_default_replace_range = function(self) source.get_default_replace_range = function(self)
if not self.context then if not self.context then
error('context is not initialized yet.') error('context is not initialized yet.')
end end
return self.cache:ensure({ 'get_default_replace_range', self.revision }, function() return self.cache:ensure({ 'get_default_replace_range', tostring(self.revision) }, function()
local _, e = pattern.offset('^' .. '\\%(' .. self:get_keyword_pattern() .. '\\)', string.sub(self.context.cursor_line, self.offset)) local _, e = pattern.offset('^' .. '\\%(' .. self:get_keyword_pattern() .. '\\)', string.sub(self.context.cursor_line, self.offset))
return { return {
start = { start = {
line = self.context.cursor.row - 1, line = self.context.cursor.row - 1,
character = misc.to_utfindex(self.context.cursor_line, self.offset), character = self.offset,
}, },
['end'] = { ['end'] = {
line = self.context.cursor.row - 1, line = self.context.cursor.row - 1,
character = misc.to_utfindex(self.context.cursor_line, e and self.offset + e - 1 or self.context.cursor.col), character = (e and self.offset + e - 2 or self.context.cursor.col - 1),
}, },
} }
end) end)
@@ -223,7 +222,10 @@ source.get_keyword_pattern = function(self)
return c.keyword_pattern return c.keyword_pattern
end end
if self.source.get_keyword_pattern then if self.source.get_keyword_pattern then
return self.source:get_keyword_pattern(misc.copy(c)) local keyword_pattern = self.source:get_keyword_pattern(misc.copy(c))
if keyword_pattern then
return keyword_pattern
end
end end
return config.get().completion.keyword_pattern return config.get().completion.keyword_pattern
end end
@@ -239,21 +241,30 @@ source.get_keyword_length = function(self)
end end
---Get filter ---Get filter
--@return function --@return fun(entry: cmp.Entry, context: cmp.Context): boolean
source.get_entry_filter = function(self) source.get_entry_filter = function(self)
local c = self:get_source_config() local c = self:get_source_config()
if c.entry_filter then if c.entry_filter then
return c.entry_filter return c.entry_filter --[[@as fun(entry: cmp.Entry, context: cmp.Context): boolean]]
end end
return function(_, _) return function(_, _)
return true return true
end end
end end
---Get lsp.PositionEncodingKind
---@return lsp.PositionEncodingKind
source.get_position_encoding_kind = function(self)
if self.source.get_position_encoding_kind then
return self.source:get_position_encoding_kind()
end
return types.lsp.PositionEncodingKind.UTF16
end
---Invoke completion ---Invoke completion
---@param ctx cmp.Context ---@param ctx cmp.Context
---@param callback function ---@param callback function
---@return boolean Return true if not trigger completion. ---@return boolean? Return true if not trigger completion.
source.complete = function(self, ctx, callback) source.complete = function(self, ctx, callback)
local offset = ctx:get_offset(self:get_keyword_pattern()) local offset = ctx:get_offset(self:get_keyword_pattern())
@@ -318,6 +329,7 @@ source.complete = function(self, ctx, callback)
completion_context = completion_context, completion_context = completion_context,
}), }),
self.complete_dedup(vim.schedule_wrap(function(response) self.complete_dedup(vim.schedule_wrap(function(response)
---@type lsp.CompletionResponse
response = response or {} response = response or {}
self.incomplete = response.isIncomplete or false self.incomplete = response.isIncomplete or false
@@ -331,7 +343,7 @@ source.complete = function(self, ctx, callback)
self.entries = {} self.entries = {}
for i, item in ipairs(response.items or response) do for i, item in ipairs(response.items or response) do
if (misc.safe(item) or {}).label then if (misc.safe(item) or {}).label then
local e = entry.new(ctx, self, item) local e = entry.new(ctx, self, item, response.itemDefaults)
self.entries[i] = e self.entries[i] = e
self.offset = math.min(self.offset, e:get_offset()) self.offset = math.min(self.offset, e:get_offset())
end end

View File

@@ -4,10 +4,18 @@ local misc = require('cmp.utils.misc')
---@class lsp ---@class lsp
local lsp = {} local lsp = {}
---@enum lsp.PositionEncodingKind
lsp.PositionEncodingKind = {
UTF8 = 'utf-8',
UTF16 = 'utf-16',
UTF32 = 'utf-32',
}
lsp.Position = { lsp.Position = {
---Convert lsp.Position to vim.Position ---Convert lsp.Position to vim.Position
---@param buf integer ---@param buf integer
---@param position lsp.Position ---@param position lsp.Position
--
---@return vim.Position ---@return vim.Position
to_vim = function(buf, position) to_vim = function(buf, position)
if not vim.api.nvim_buf_is_loaded(buf) then if not vim.api.nvim_buf_is_loaded(buf) then
@@ -45,11 +53,77 @@ lsp.Position = {
character = position.col - 1, character = position.col - 1,
} }
end, end,
---Convert position to utf8 from specified encoding.
---@param text string
---@param position lsp.Position
---@param from_encoding? lsp.PositionEncodingKind
---@return lsp.Position
to_utf8 = function(text, position, from_encoding)
from_encoding = from_encoding or lsp.PositionEncodingKind.UTF16
if from_encoding == lsp.PositionEncodingKind.UTF8 then
return position
end
local ok, byteindex = pcall(function()
return vim.str_byteindex(text, position.character, from_encoding == lsp.PositionEncodingKind.UTF16)
end)
if not ok then
return position
end
return { line = position.line, character = byteindex }
end,
---Convert position to utf16 from specified encoding.
---@param text string
---@param position lsp.Position
---@param from_encoding? lsp.PositionEncodingKind
---@return lsp.Position
to_utf16 = function(text, position, from_encoding)
from_encoding = from_encoding or lsp.PositionEncodingKind.UTF16
if from_encoding == lsp.PositionEncodingKind.UTF16 then
return position
end
local utf8 = lsp.Position.to_utf8(text, position, from_encoding)
for index = utf8.character, 0, -1 do
local ok, utf16index = pcall(function()
return select(2, vim.str_utfindex(text, index))
end)
if ok then
return { line = utf8.line, character = utf16index }
end
end
return position
end,
---Convert position to utf32 from specified encoding.
---@param text string
---@param position lsp.Position
---@param from_encoding? lsp.PositionEncodingKind
---@return lsp.Position
to_utf32 = function(text, position, from_encoding)
from_encoding = from_encoding or lsp.PositionEncodingKind.UTF16
if from_encoding == lsp.PositionEncodingKind.UTF32 then
return position
end
local utf8 = lsp.Position.to_utf8(text, position, from_encoding)
for index = utf8.character, 0, -1 do
local ok, utf32index = pcall(function()
return select(1, vim.str_utfindex(text, index))
end)
if ok then
return { line = utf8.line, character = utf32index }
end
end
return position
end
} }
lsp.Range = { lsp.Range = {
---Convert lsp.Range to vim.Range ---Convert lsp.Range to vim.Range
---@param buf integer|string ---@param buf integer
---@param range lsp.Range ---@param range lsp.Range
---@return vim.Range ---@return vim.Range
to_vim = function(buf, range) to_vim = function(buf, range)
@@ -60,7 +134,7 @@ lsp.Range = {
end, end,
---Convert vim.Range to lsp.Range ---Convert vim.Range to lsp.Range
---@param buf integer|string ---@param buf integer
---@param range vim.Range ---@param range vim.Range
---@return lsp.Range ---@return lsp.Range
to_lsp = function(buf, range) to_lsp = function(buf, range)
@@ -130,15 +204,23 @@ lsp.CompletionItemKind = {
} }
lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind) lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind)
---@class lsp.internal.CompletionItemDefaults
---@field public commitCharacters? string[]
---@field public editRange? lsp.Range | { insert: lsp.Range, replace: lsp.Range }
---@field public insertTextFormat? lsp.InsertTextFormat
---@field public insertTextMode? lsp.InsertTextMode
---@field public data? any
---@class lsp.CompletionContext ---@class lsp.CompletionContext
---@field public triggerKind lsp.CompletionTriggerKind ---@field public triggerKind lsp.CompletionTriggerKind
---@field public triggerCharacter string|nil ---@field public triggerCharacter string|nil
---@class lsp.CompletionList ---@class lsp.CompletionList
---@field public isIncomplete boolean ---@field public isIncomplete boolean
---@field public itemDefaults? lsp.internal.CompletionItemDefaults
---@field public items lsp.CompletionItem[] ---@field public items lsp.CompletionItem[]
---@alias lsp.CompletionResponse lsp.CompletionList|lsp.CompletionItem[]|nil ---@alias lsp.CompletionResponse lsp.CompletionList|lsp.CompletionItem[]
---@class lsp.MarkupContent ---@class lsp.MarkupContent
---@field public kind lsp.MarkupKind ---@field public kind lsp.MarkupKind
@@ -168,37 +250,38 @@ lsp.CompletionItemKind = vim.tbl_add_reverse_lookup(lsp.CompletionItemKind)
---@field public newText string ---@field public newText string
---@class lsp.internal.ReplaceTextEdit ---@class lsp.internal.ReplaceTextEdit
---@field public insert lsp.Range ---@field public replace lsp.Range
---@field public newText string ---@field public newText string
---@class lsp.CompletionItemLabelDetails ---@class lsp.CompletionItemLabelDetails
---@field public detail string|nil ---@field public detail? string
---@field public description string|nil ---@field public description? string
---@class lsp.Cmp ---@class lsp.internal.CmpCompletionExtension
---@field public kind_text string ---@field public kind_text string
---@field public kind_hl_group string ---@field public kind_hl_group string
---@class lsp.CompletionItem ---@class lsp.CompletionItem
---@field public label string ---@field public label string
---@field public labelDetails lsp.CompletionItemLabelDetails|nil ---@field public labelDetails? lsp.CompletionItemLabelDetails
---@field public kind lsp.CompletionItemKind|nil ---@field public kind? lsp.CompletionItemKind
---@field public tags lsp.CompletionItemTag[]|nil ---@field public tags? lsp.CompletionItemTag[]
---@field public detail string|nil ---@field public detail? string
---@field public documentation lsp.MarkupContent|string|nil ---@field public documentation? lsp.MarkupContent|string
---@field public deprecated boolean|nil ---@field public deprecated? boolean
---@field public preselect boolean|nil ---@field public preselect? boolean
---@field public sortText string|nil ---@field public sortText? string
---@field public filterText string|nil ---@field public filterText? string
---@field public insertText string|nil ---@field public insertText? string
---@field public insertTextFormat lsp.InsertTextFormat ---@field public insertTextFormat? lsp.InsertTextFormat
---@field public insertTextMode lsp.InsertTextMode ---@field public insertTextMode? lsp.InsertTextMode
---@field public textEdit lsp.TextEdit|lsp.InsertReplaceTextEdit|nil ---@field public textEdit? lsp.TextEdit|lsp.InsertReplaceTextEdit
---@field public additionalTextEdits lsp.TextEdit[] ---@field public textEditText? string
---@field public commitCharacters string[]|nil ---@field public additionalTextEdits? lsp.TextEdit[]
---@field public command lsp.Command|nil ---@field public commitCharacters? string[]
---@field public data any|nil ---@field public command? lsp.Command
---@field public cmp lsp.Cmp|nil ---@field public data? any
---@field public cmp? lsp.internal.CmpCompletionExtension
--- ---
---TODO: Should send the issue for upstream? ---TODO: Should send the issue for upstream?
---@field public word string|nil ---@field public word string|nil

View File

@@ -11,7 +11,7 @@
---@field public kind_hl_group string|nil ---@field public kind_hl_group string|nil
---@field public menu_hl_group string|nil ---@field public menu_hl_group string|nil
---@class vim.Position ---@class vim.Position 1-based index
---@field public row integer ---@field public row integer
---@field public col integer ---@field public col integer

View File

@@ -83,6 +83,21 @@ keymap.backspace = function(count)
return table.concat(keys, '') return table.concat(keys, '')
end end
---Create delete keys.
---@param count string|integer
---@return string
keymap.delete = function(count)
if type(count) == 'string' then
count = vim.fn.strchars(count, true)
end
if count <= 0 then
return ''
end
local keys = {}
table.insert(keys, keymap.t(string.rep('<Del>', count)))
return table.concat(keys, '')
end
---Update indentkeys. ---Update indentkeys.
---@param expr? string ---@param expr? string
---@return string ---@return string