From 7e097da01f2e3e72db419d664fed543b922a5f30 Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Fri, 6 Aug 2021 22:38:40 +0900 Subject: [PATCH] Improve expansion and dot-repeatable --- lua/cmp/context.lua | 6 +- lua/cmp/core.lua | 143 ++++++++++++++++++++---------------------- lua/cmp/utils/str.lua | 8 ++- 3 files changed, 78 insertions(+), 79 deletions(-) diff --git a/lua/cmp/context.lua b/lua/cmp/context.lua index 2e12745..b51e977 100644 --- a/lua/cmp/context.lua +++ b/lua/cmp/context.lua @@ -14,7 +14,7 @@ local cache = require('cmp.utils.cache') ---@field public time number ---@field public mode string ---@field public bufnr number ----@field public cursor vim.Position +---@field public cursor vim.Position|lsp.Position ---@field public cursor_line string ---@field public cursor_after_line string ---@field public cursor_before_line string @@ -51,10 +51,12 @@ context.new = function(prev_context, option) self.time = vim.loop.now() self.mode = vim.api.nvim_get_mode().mode self.bufnr = vim.api.nvim_get_current_buf() + self.cursor_line = vim.api.nvim_get_current_line() self.cursor = {} self.cursor.row = vim.api.nvim_win_get_cursor(0)[1] self.cursor.col = vim.api.nvim_win_get_cursor(0)[2] + 1 - self.cursor_line = vim.api.nvim_get_current_line() + self.cursor.line = self.cursor.row - 1 + self.cursor.character = vim.str_utfindex(self.cursor_line, self.cursor.col - 1) 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 diff --git a/lua/cmp/core.lua b/lua/cmp/core.lua index 2f32ef9..7d8a7f7 100644 --- a/lua/cmp/core.lua +++ b/lua/cmp/core.lua @@ -192,91 +192,86 @@ core.confirm = vim.schedule_wrap(function(e, option, callback) debug.log('entry.confirm', e:get_completion_item()) - --@see https://github.com/microsoft/vscode/blob/main/src/vs/editor/contrib/suggest/suggestController.ts#L334 - local pre = context.new() - if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then - local new = context.new(pre) - e:resolve(function() - 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) - end - - -- Prepare completion item for confirmation - 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.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 - - if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) > 0 then - vim.fn['cmp#apply_text_edits'](pre.bufnr, e:get_completion_item().additionalTextEdits) - end - - -- First, emulates vim's `` behavior and then confirms LSP functionalities. - local range = types.lsp.Range.to_vim(pre.bufnr, completion_item.textEdit.range) - local before_text = string.sub(pre.cursor_line, range.start.col, pre.cursor.col - 1) - local after_text = string.sub(pre.cursor_line, pre.cursor.col, pre.cursor.col + (range['end'].col - e.context.cursor.col) - 1) - local before_len = vim.fn.strchars(before_text) - local after_len = vim.fn.strchars(after_text) - local keys = 'u' .. string.rep('U', after_len) .. string.rep('', before_len + after_len) - if not completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then - keys = keys .. completion_item.textEdit.newText - else - keys = keys .. e:get_word() - end + local ctx = context.new() + local restore_text = string.sub(ctx.cursor_line, e.context.cursor.col, ctx.cursor.col - 1) + local restore_keys = string.rep('', vim.fn.strchars(restore_text)) keymap.feedkeys( - keys, + 'U' .. restore_keys, 'n', vim.schedule_wrap(function() - local execute = function() - e:execute(function() - if callback then - callback() + --@see https://github.com/microsoft/vscode/blob/main/src/vs/editor/contrib/suggest/suggestController.ts#L334 + 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'](ctx.bufnr, e:get_completion_item().additionalTextEdits) end - if completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then - keymap.feedkeys( - 'U' .. string.rep('', vim.fn.strchars(e:get_word())), - 'n', - vim.schedule_wrap(function() + + -- Prepare completion item for confirmation + 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.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 is_snippet = vim.lsp.util.parse_snippet(completion_item.textEdit.newText) ~= completion_item.textEdit.newText + + local keys = '' + if completion_item.textEdit.range['end'].character > e.context.cursor.character then + keys = keys .. string.rep('U', completion_item.textEdit.range['end'].character - e.context.cursor.character) + end + if e.context.cursor.character > completion_item.textEdit.range.start.character then + keys = keys .. string.rep('', e.context.cursor.character - completion_item.textEdit.range.start.character) + end + if not is_snippet then + keys = keys .. 'u' .. completion_item.textEdit.newText + end + keymap.feedkeys( + keys, + 'n', + vim.schedule_wrap(function() + if is_snippet then config.get().snippet.expand({ body = completion_item.textEdit.newText, insert_text_mode = completion_item.insertTextMode, }) - execute() + end + e:execute(function() + if callback then + callback() + end end) - ) - else - execute() - end + end) + ) end) ) end) diff --git a/lua/cmp/utils/str.lua b/lua/cmp/utils/str.lua index 185e1a9..2ad353a 100644 --- a/lua/cmp/utils/str.lua +++ b/lua/cmp/utils/str.lua @@ -4,13 +4,15 @@ 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("'")] = true -INVALID_CHARS[string.byte("\n")] = true +INVALID_CHARS[string.byte(' ')] = true +INVALID_CHARS[string.byte('\t')] = true +INVALID_CHARS[string.byte('\n')] = true local PAIR_CHARS = {} PAIR_CHARS[string.byte('[')] = string.byte(']')