Skip to content

Commit ec62fd6

Browse files
authored
feat: leverage virt_text_inline to improve inline suggestions (#465)
fixes #328
1 parent 935ad69 commit ec62fd6

File tree

3 files changed

+109
-13
lines changed

3 files changed

+109
-13
lines changed

lua/copilot/suggestion/init.lua

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ local config = require("copilot.config")
44
local hl_group = require("copilot.highlight").group
55
local util = require("copilot.util")
66
local logger = require("copilot.logger")
7+
local suggestion_util = require("copilot.suggestion.utils")
78

89
local M = {}
910

@@ -56,6 +57,27 @@ local function get_ctx(bufnr)
5657
return ctx
5758
end
5859

60+
---@param idx integer
61+
---@param text string
62+
---@param bufnr? integer
63+
local function set_ctx_suggestion_text(idx, text, bufnr)
64+
bufnr = bufnr or vim.api.nvim_get_current_buf()
65+
66+
if not copilot.context[bufnr] then
67+
return
68+
end
69+
70+
if not copilot.context[bufnr].suggestions[idx] then
71+
return
72+
end
73+
74+
local suggestion = copilot.context[bufnr].suggestions[idx]
75+
local end_offset = #suggestion.text - #text
76+
suggestion.text = text
77+
suggestion.range["end"].character = suggestion.range["end"].character - end_offset
78+
copilot.context[bufnr].suggestions[idx] = suggestion
79+
end
80+
5981
---@param ctx copilot_suggestion_context
6082
local function reset_ctx(ctx)
6183
logger.trace("suggestion reset context", ctx)
@@ -255,8 +277,6 @@ local function update_preview(ctx)
255277
return
256278
end
257279

258-
---@todo support popup preview
259-
260280
local annot = ""
261281
if ctx.cycling_callbacks then
262282
annot = "(1/…)"
@@ -265,14 +285,25 @@ local function update_preview(ctx)
265285
end
266286

267287
local cursor_col = vim.fn.col(".")
288+
local cursor_line = vim.fn.line(".") - 1
289+
local current_line = vim.api.nvim_buf_get_lines(0, cursor_line, cursor_line + 1, false)[1]
290+
local text_after_cursor = string.sub(current_line, cursor_col)
268291

269292
displayLines[1] =
270293
string.sub(string.sub(suggestion.text, 1, (string.find(suggestion.text, "\n", 1, true) or 0) - 1), cursor_col)
271294

295+
local suggestion_line1 = displayLines[1]
296+
297+
if #displayLines == 1 then
298+
suggestion_line1 = suggestion_util.remove_common_suffix(text_after_cursor, suggestion_line1)
299+
local suggest_text = suggestion_util.remove_common_suffix(text_after_cursor, suggestion.text)
300+
set_ctx_suggestion_text(ctx.choice, suggest_text)
301+
end
302+
272303
local extmark = {
273304
id = copilot.extmark_id,
274-
virt_text_win_col = vim.fn.virtcol(".") - 1,
275-
virt_text = { { displayLines[1], hl_group.CopilotSuggestion } },
305+
virt_text = { { suggestion_line1, hl_group.CopilotSuggestion } },
306+
virt_text_pos = "inline",
276307
}
277308

278309
if #displayLines > 1 then
@@ -289,7 +320,6 @@ local function update_preview(ctx)
289320
end
290321

291322
extmark.hl_mode = "combine"
292-
293323
vim.api.nvim_buf_set_extmark(0, copilot.ns_id, vim.fn.line(".") - 1, cursor_col - 1, extmark)
294324

295325
if config.suggestion.suggestion_notification then
@@ -540,14 +570,17 @@ function M.accept(modifier)
540570
and vim.api.nvim_get_option_value("fileencoding", { buf = bufnr })
541571
or vim.api.nvim_get_option_value("encoding", { scope = "global" })
542572
vim.lsp.util.apply_text_edits({ { range = range, newText = newText } }, bufnr, encoding)
543-
-- Put cursor at the end of current line.
544-
local cursor_keys = "<End>"
545-
546-
-- TODO: Move to util and check only once
547-
if vim.fn.has("nvim-0.10") == 1 then
548-
cursor_keys = string.rep("<Down>", #vim.split(newText, "\n", { plain = true }) - 1) .. cursor_keys
549-
end
550-
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(cursor_keys, true, false, true), "n", false)
573+
print(range["end"].line)
574+
print(range["end"].character)
575+
576+
-- instead of calling <End>, go to the pos of the row after the last \n of inserted text
577+
-- local cursor_keys = string.rep("<Down>", #vim.split(newText, "\n", { plain = true }) - 1) .. "<End>"
578+
-- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(cursor_keys, true, false, true), "n", false)
579+
local lines = vim.split(newText, "\n", { plain = true })
580+
local last_line = lines[#lines]
581+
local cursor_keys = string.rep("<Down>", #lines - 1)
582+
-- Position cursor at the end of the last inserted line
583+
vim.api.nvim_win_set_cursor(0, { range["start"].line + #lines, #last_line })
551584
end)()
552585
end
553586

lua/copilot/suggestion/utils.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
local M = {}
2+
3+
function M.remove_common_suffix(str, suggestion)
4+
if str == "" or suggestion == "" then
5+
return suggestion
6+
end
7+
8+
local str_len = #str
9+
local suggestion_len = #suggestion
10+
local shorter_len = math.min(str_len, suggestion_len)
11+
12+
local matching = 0
13+
for i = 1, shorter_len do
14+
local str_char = string.sub(str, str_len - i + 1, str_len - i + 1)
15+
local suggestion_char = string.sub(suggestion, suggestion_len - i + 1, suggestion_len - i + 1)
16+
17+
if str_char == suggestion_char then
18+
matching = matching + 1
19+
else
20+
break
21+
end
22+
end
23+
24+
if matching == 0 then
25+
return suggestion
26+
end
27+
28+
return string.sub(suggestion, 1, suggestion_len - matching)
29+
end
30+
31+
return M

tests/test_suggestion_util.lua

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
local u = require("copilot.suggestion.utils")
2+
local eq = MiniTest.expect.equality
3+
4+
local T = MiniTest.new_set({
5+
hooks = {
6+
pre_once = function() end,
7+
pre_case = function() end,
8+
},
9+
})
10+
11+
T["suggestion_utils()"] = MiniTest.new_set()
12+
13+
T["suggestion_utils()"]["remove common suffix"] = function()
14+
local test_cases = {
15+
{ [[event1 = ("test", ),]], [["test2"),]], [["test2"]] },
16+
-- ^
17+
{ [[event2 = ("test", ""),]], [[test2"),]], [[test2]] },
18+
-- ^
19+
{ [[event3 = ("test", ]], [["test2"),]], [["test2"),]] },
20+
-- ^
21+
{ [[event4 = ("test", ]], "", "" },
22+
{ "", [[("test"),]], [[("test"),]] },
23+
}
24+
25+
for _, case in ipairs(test_cases) do
26+
local str, substr, expected = unpack(case)
27+
local result = u.remove_common_suffix(str, substr)
28+
eq(result, expected)
29+
end
30+
end
31+
32+
return T

0 commit comments

Comments
 (0)