From ebbb222668bfa1faf460519630870902bfa53e34 Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Tue, 23 Jan 2024 04:28:26 -0800 Subject: [PATCH] feat: support completion in terminal mode --- lua/cmp/core.lua | 189 ++++++++++++++++++++++-------------------- lua/cmp/init.lua | 4 +- lua/cmp/utils/api.lua | 21 ++++- 3 files changed, 119 insertions(+), 95 deletions(-) diff --git a/lua/cmp/core.lua b/lua/cmp/core.lua index b34c360ec..b26194764 100644 --- a/lua/cmp/core.lua +++ b/lua/cmp/core.lua @@ -387,7 +387,7 @@ core.confirm = function(self, e, option, callback) 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())) feedkeys.call(table.concat(keys, ''), 'in') - else + elseif not api.is_terminal_mode() then vim.cmd([[silent! undojoin]]) -- This logic must be used nvim_buf_set_text. -- If not used, the snippet engine's placeholder wil be broken. @@ -397,107 +397,112 @@ core.confirm = function(self, e, option, callback) vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 }) end end) - feedkeys.call('', 'n', function() - -- Apply additionalTextEdits. - local ctx = context.new() - if #(e:get_completion_item().additionalTextEdits or {}) == 0 then - e:resolve(function() - local new = context.new() - local text_edits = e:get_completion_item().additionalTextEdits or {} - if #text_edits == 0 then - return - end + -- if api.is_terminal_mode() then + -- return + -- end + if not api.is_terminal_mode() then + feedkeys.call('', 'n', function() + -- Apply additionalTextEdits. + local ctx = context.new() + if #(e:get_completion_item().additionalTextEdits or {}) == 0 then + e:resolve(function() + local new = context.new() + local text_edits = e:get_completion_item().additionalTextEdits or {} + if #text_edits == 0 then + return + end - local has_cursor_line_text_edit = (function() - local minrow = math.min(ctx.cursor.row, new.cursor.row) - local maxrow = math.max(ctx.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 + local has_cursor_line_text_edit = (function() + local minrow = math.min(ctx.cursor.row, new.cursor.row) + local maxrow = math.max(ctx.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 - return false - end)() - if has_cursor_line_text_edit then - return - end + vim.cmd([[silent! undojoin]]) + vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, e.source:get_position_encoding_kind()) + end) + else vim.cmd([[silent! undojoin]]) - vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, e.source:get_position_encoding_kind()) - end) - else - vim.cmd([[silent! undojoin]]) - vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, e.source:get_position_encoding_kind()) - end - end) - feedkeys.call('', 'n', function() - local ctx = context.new() - local completion_item = misc.copy(e:get_completion_item()) - if not completion_item.textEdit then - completion_item.textEdit = {} - local insertText = completion_item.insertText - if misc.empty(insertText) then - insertText = nil + vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, e.source:get_position_encoding_kind()) end - completion_item.textEdit.newText = 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 = 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 + 1) - e.context.cursor.col) - local new_text = completion_item.textEdit.newText - completion_item.textEdit.range.start.line = ctx.cursor.line - completion_item.textEdit.range.start.character = (ctx.cursor.col - 1) - diff_before - completion_item.textEdit.range['end'].line = ctx.cursor.line - completion_item.textEdit.range['end'].character = (ctx.cursor.col - 1) + diff_after - if api.is_insert_mode() then - if false then - --To use complex expansion debug. - vim.print({ -- luacheck: ignore - item = e:get_completion_item(), - diff_before = diff_before, - diff_after = diff_after, - new_text = new_text, - text_edit_new_text = completion_item.textEdit.newText, - range_start = completion_item.textEdit.range.start.character, - range_end = completion_item.textEdit.range['end'].character, - original_range_start = e:get_completion_item().textEdit.range.start.character, - original_range_end = e:get_completion_item().textEdit.range['end'].character, - cursor_line = ctx.cursor_line, - cursor_col0 = ctx.cursor.col - 1, - }) + end) + feedkeys.call('', 'n', function() + local ctx = context.new() + local completion_item = misc.copy(e:get_completion_item()) + if not completion_item.textEdit then + completion_item.textEdit = {} + local insertText = completion_item.insertText + if misc.empty(insertText) then + insertText = nil + end + completion_item.textEdit.newText = insertText or completion_item.word or completion_item.label end - local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet - if is_snippet then - completion_item.textEdit.newText = '' + 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 - vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-8') - local texts = vim.split(completion_item.textEdit.newText, '\n') - vim.api.nvim_win_set_cursor(0, { - completion_item.textEdit.range.start.line + #texts, - (#texts == 1 and (completion_item.textEdit.range.start.character + #texts[1]) or #texts[#texts]), - }) - if is_snippet then - config.get().snippet.expand({ - body = new_text, - insert_text_mode = completion_item.insertTextMode, + 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 + 1) - e.context.cursor.col) + local new_text = completion_item.textEdit.newText + completion_item.textEdit.range.start.line = ctx.cursor.line + completion_item.textEdit.range.start.character = (ctx.cursor.col - 1) - diff_before + completion_item.textEdit.range['end'].line = ctx.cursor.line + completion_item.textEdit.range['end'].character = (ctx.cursor.col - 1) + diff_after + if api.is_insert_mode() then + if false then + --To use complex expansion debug. + vim.print({ -- luacheck: ignore + item = e:get_completion_item(), + diff_before = diff_before, + diff_after = diff_after, + new_text = new_text, + text_edit_new_text = completion_item.textEdit.newText, + range_start = completion_item.textEdit.range.start.character, + range_end = completion_item.textEdit.range['end'].character, + original_range_start = e:get_completion_item().textEdit.range.start.character, + original_range_end = e:get_completion_item().textEdit.range['end'].character, + cursor_line = ctx.cursor_line, + cursor_col0 = ctx.cursor.col - 1, + }) + end + local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet + if is_snippet then + completion_item.textEdit.newText = '' + end + vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-8') + + local texts = vim.split(completion_item.textEdit.newText, '\n') + vim.api.nvim_win_set_cursor(0, { + completion_item.textEdit.range.start.line + #texts, + (#texts == 1 and (completion_item.textEdit.range.start.character + #texts[1]) or #texts[#texts]), }) + if is_snippet then + config.get().snippet.expand({ + body = new_text, + insert_text_mode = completion_item.insertTextMode, + }) + end + else + local keys = {} + table.insert(keys, keymap.backspace(ctx.cursor_line:sub(completion_item.textEdit.range.start.character + 1, ctx.cursor.col - 1))) + table.insert(keys, keymap.delete(ctx.cursor_line:sub(ctx.cursor.col, completion_item.textEdit.range['end'].character))) + table.insert(keys, new_text) + feedkeys.call(table.concat(keys, ''), 'in') end - else - local keys = {} - table.insert(keys, keymap.backspace(ctx.cursor_line:sub(completion_item.textEdit.range.start.character + 1, ctx.cursor.col - 1))) - table.insert(keys, keymap.delete(ctx.cursor_line:sub(ctx.cursor.col, completion_item.textEdit.range['end'].character))) - table.insert(keys, new_text) - feedkeys.call(table.concat(keys, ''), 'in') - end - end) + end) + end feedkeys.call(keymap.indentkeys(vim.bo.indentkeys), 'n') feedkeys.call('', 'n', function() e:execute(vim.schedule_wrap(function() diff --git a/lua/cmp/init.lua b/lua/cmp/init.lua index 4aaf2fe6c..edbc2033d 100644 --- a/lua/cmp/init.lua +++ b/lua/cmp/init.lua @@ -338,7 +338,7 @@ local on_text_changed = function() cmp.core:on_change('TextChanged') end end -autocmd.subscribe({ 'TextChangedI', 'TextChangedP' }, on_text_changed) +autocmd.subscribe({ 'TextChangedI', 'TextChangedP', 'TextChangedT' }, on_text_changed) autocmd.subscribe('CmdlineChanged', async.debounce_next_tick(on_text_changed)) autocmd.subscribe('CursorMovedI', function() @@ -351,7 +351,7 @@ autocmd.subscribe('CursorMovedI', function() end) -- If make this asynchronous, the completion menu will not close when the command output is displayed. -autocmd.subscribe({ 'InsertLeave', 'CmdlineLeave' }, function() +autocmd.subscribe({ 'InsertLeave', 'CmdlineLeave', 'TermLeave' }, function() cmp.core:reset() cmp.core.view:close() end) diff --git a/lua/cmp/utils/api.lua b/lua/cmp/utils/api.lua index f304a78e3..f6c0dc4fe 100644 --- a/lua/cmp/utils/api.lua +++ b/lua/cmp/utils/api.lua @@ -13,6 +13,8 @@ api.get_mode = function() return 's' -- select elseif mode == 'c' and vim.fn.getcmdtype() ~= '=' then return 'c' -- cmdline + elseif mode == 't' then + return 't' -- terminal end end @@ -32,15 +34,22 @@ api.is_visual_mode = function() return api.get_mode() == 'x' end +api.is_terminal_mode = function() + return api.get_mode() == 't' +end + api.is_suitable_mode = function() local mode = api.get_mode() - return mode == 'i' or mode == 'c' + return mode == 't' or mode == 'c' or mode == 'i' end api.get_current_line = function() if api.is_cmdline_mode() then return vim.fn.getcmdline() end + if api.is_terminal_mode() then + return vim.api.nvim_get_current_line() --:sub(4) + end return vim.api.nvim_get_current_line() end @@ -64,6 +73,16 @@ end api.get_cursor_before_line = function() local cursor = api.get_cursor() + if api.is_terminal_mode() then + local line = vim.api.nvim_get_current_line() + local prompt = 7 -- TODO: this needs to be dynamic or configurable + line = line:sub(prompt) + cursor[2] = cursor[2] - prompt + local start = line:match('%s*([^%s])*') or 1 + line = line:sub(start) + cursor[2] = cursor[2] - #line + return vim.print(line:sub(1, cursor[2])) + end return string.sub(api.get_current_line(), 1, cursor[2]) end