Skip to content

Commit

Permalink
feat: support completion in terminal mode
Browse files Browse the repository at this point in the history
  • Loading branch information
willothy committed Jan 23, 2024
1 parent 538e37b commit e47c774
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 95 deletions.
189 changes: 97 additions & 92 deletions lua/cmp/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions lua/cmp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand Down
21 changes: 20 additions & 1 deletion lua/cmp/utils/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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 line:sub(1, cursor[2])
end
return string.sub(api.get_current_line(), 1, cursor[2])
end

Expand Down

0 comments on commit e47c774

Please sign in to comment.