Skip to content

Commit bc2223e

Browse files
tris203Xuyuanp
andcommitted
feat(lsp): enhance copilot inline suggestions
- Set custom highlight groups for NesAdd, NesDelete, and NesApply. - Add keymap to apply pending inline suggestions on <leader>xa. - Comment out extraneous debug output in completion handler. - Refactor inline edit application using LSP util function. Co-authored-by: Xuyaun Pang <[email protected]>
1 parent 37a9de0 commit bc2223e

File tree

6 files changed

+174
-55
lines changed

6 files changed

+174
-55
lines changed

lsp/copilot.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ return {
1919
},
2020
root_markers = { ".git" },
2121
on_init = function(client)
22+
vim.api.nvim_set_hl(0, "NesAdd", { link = "DiffAdd", default = true })
23+
vim.api.nvim_set_hl(0, "NesDelete", { link = "DiffDelete", default = true })
24+
vim.api.nvim_set_hl(0, "NesApply", { link = "DiffText", default = true })
25+
2226
local nes = require("copilot-lsp.nes")
2327
local inline_completion = require("copilot-lsp.completion")
2428

@@ -30,6 +34,10 @@ return {
3034
nes.request_nes(client)
3135
end)
3236

37+
vim.keymap.set("n", "<leader>xa", function()
38+
nes.apply_pending_nes(nil, { trigger = true, jump = true }, client)
39+
end)
40+
3341
local au = vim.api.nvim_create_augroup("copilot-language-server", { clear = true })
3442
vim.api.nvim_create_autocmd("TextChangedI", {
3543
callback = function()

lua/copilot-lsp/completion/init.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ local function handle_inlineCompletion_response(results, _ctx, _config)
1818

1919
for _, result in pairs(results1) do
2020
-- This is where we show the completion results
21-
dd(result)
21+
-- dd(result)
2222
end
2323
end
2424

lua/copilot-lsp/nes/init.lua

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,23 @@
11
local errs = require("copilot-lsp.errors")
2+
local nes_ui = require("copilot-lsp.nes.ui")
23
local utils = require("copilot-lsp.util")
34

45
local M = {}
56

6-
local nes_ext
77
local nes_ns = vim.api.nvim_create_namespace("copilot-nes")
88

9-
---@param edit copilotInlineEdit
10-
local function display_nes(edit)
11-
dd("trying to display")
12-
local bufnr = vim.uri_to_bufnr(edit.textDocument.uri)
13-
if edit.text:match("\n") then
14-
assert(false, "multi line edits not supported yet")
15-
end
16-
17-
nes_ext = vim.api.nvim_buf_set_extmark(bufnr, nes_ns, edit.range.start.line, edit.range.start.character, {
18-
id = nes_ext,
19-
virt_lines = { { { edit.text, "Comment" } } },
20-
})
21-
22-
--create accept and decline keymaps
23-
vim.keymap.set("n", "<leader>xa", function()
24-
utils.apply_inline_edit(edit)
25-
vim.api.nvim_buf_del_extmark(bufnr, nes_ns, nes_ext)
26-
end, { buffer = bufnr })
27-
28-
vim.keymap.set("n", "<leader>xd", function()
29-
vim.api.nvim_buf_del_extmark(bufnr, nes_ns, nes_ext)
30-
end, { buffer = bufnr })
31-
end
32-
339
---@param err lsp.ResponseError?
34-
---@param result copilotInlineEditResponse
10+
---@param result copilotlsp.copilotInlineEditResponse
3511
local function handle_nes_response(err, result)
3612
if err then
3713
vim.notify(err.message)
3814
return
3915
end
40-
if #result.edits > 1 then
41-
vim.notify("more than 1 edit, dont know what to do yet")
42-
return
43-
end
44-
45-
if #result.edits == 0 then
46-
vim.notify("no edits")
47-
return
16+
for _, edit in ipairs(result.edits) do
17+
--- Convert to textEdit fields
18+
edit.newText = edit.text
4819
end
49-
50-
local edit = result.edits[1]
51-
display_nes(edit)
52-
-- utils.apply_inline_edit(edit)
20+
nes_ui._display_next_suggestion(result.edits, nes_ns)
5321
end
5422

5523
---@param copilot_lss vim.lsp.Client?
@@ -62,4 +30,26 @@ function M.request_nes(copilot_lss)
6230
copilot_lss:request("textDocument/copilotInlineEdit", pos_params, handle_nes_response)
6331
end
6432

33+
---@param bufnr? integer
34+
---@param opts? nes.Apply.Opts
35+
---@param client vim.lsp.Client
36+
function M.apply_pending_nes(bufnr, opts, client)
37+
opts = opts or {}
38+
39+
bufnr = bufnr and bufnr > 0 and bufnr or vim.api.nvim_get_current_buf()
40+
41+
---@type copilotlsp.InlineEdit
42+
local state = vim.b[bufnr].nes_state
43+
if not state then
44+
return
45+
end
46+
utils.apply_inline_edit(state)
47+
nes_ui.clear_suggestion(bufnr, nes_ns)
48+
if opts.trigger then
49+
vim.schedule(function()
50+
M.request_nes(client)
51+
end)
52+
end
53+
end
54+
6555
return M

lua/copilot-lsp/nes/ui.lua

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
local M = {}
2+
3+
---@param bufnr integer
4+
---@param suggestion_ui nes.EditSuggestionUI
5+
---@param ns_id integer
6+
local function _dismiss_suggestion_ui(bufnr, suggestion_ui, ns_id)
7+
pcall(vim.api.nvim_win_close, suggestion_ui.preview_winnr, true)
8+
pcall(vim.api.nvim_buf_clear_namespace, bufnr, ns_id, 0, -1)
9+
end
10+
11+
---@param bufnr? integer
12+
---@param ns_id integer
13+
function M.clear_suggestion(bufnr, ns_id)
14+
bufnr = bufnr and bufnr > 0 and bufnr or vim.api.nvim_get_current_buf()
15+
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1)
16+
---@type copilotlsp.InlineEdit
17+
local state = vim.b[bufnr].nes_state
18+
if not state then
19+
return
20+
end
21+
22+
_dismiss_suggestion_ui(bufnr, state.ui, ns_id)
23+
vim.b[bufnr].nes_state = nil
24+
end
25+
26+
---@private
27+
---@param edits copilotlsp.InlineEdit[]
28+
---@param ns_id integer
29+
function M._display_next_suggestion(edits, ns_id)
30+
if not edits or #edits == 0 then
31+
vim.notify("No suggestion available", vim.log.levels.INFO)
32+
return
33+
end
34+
local bufnr = vim.uri_to_bufnr(edits[1].textDocument.uri)
35+
local win_id = vim.fn.win_findbuf(bufnr)[1]
36+
local suggestion = edits[1]
37+
38+
local ui = {}
39+
local deleted_lines_count = suggestion.range["end"].line - suggestion.range.start.line
40+
local added_lines = vim.split(suggestion.newText, "\n")
41+
local added_lines_count = suggestion.newText == "" and 0 or #added_lines - 1
42+
local same_line = 0
43+
44+
if deleted_lines_count == 0 and added_lines_count == 0 then
45+
---changing within line
46+
deleted_lines_count = 1
47+
added_lines_count = 1
48+
same_line = 1
49+
added_lines = { suggestion.newText }
50+
end
51+
52+
if deleted_lines_count > 0 then
53+
vim.api.nvim_buf_set_extmark(bufnr, ns_id, suggestion.range.start.line, 0, {
54+
hl_group = "NesDelete",
55+
end_line = suggestion.range["end"].line,
56+
})
57+
end
58+
if added_lines_count > 0 then
59+
local virt_lines = {}
60+
for _ = 1, added_lines_count do
61+
table.insert(virt_lines, {
62+
{ "", "Normal" },
63+
})
64+
end
65+
local line = suggestion.range.start.line + deleted_lines_count - 1 + same_line
66+
67+
vim.api.nvim_buf_set_extmark(bufnr, ns_id, line, 0, {
68+
virt_lines = virt_lines,
69+
})
70+
71+
local preview_bufnr = vim.api.nvim_create_buf(false, true)
72+
vim.api.nvim_buf_set_lines(preview_bufnr, 0, -1, false, added_lines)
73+
vim.bo[preview_bufnr].modifiable = false
74+
vim.bo[preview_bufnr].buflisted = false
75+
vim.bo[preview_bufnr].bufhidden = "wipe"
76+
vim.bo[preview_bufnr].filetype = vim.bo[bufnr].filetype
77+
78+
local cursor = vim.api.nvim_win_get_cursor(win_id)
79+
local win_width = vim.api.nvim_win_get_width(win_id)
80+
local offset = vim.fn.getwininfo(win_id)[1].textoff
81+
local preview_winnr = vim.api.nvim_open_win(preview_bufnr, false, {
82+
relative = "cursor",
83+
width = win_width - offset,
84+
height = (#added_lines + same_line) - 1,
85+
row = (suggestion.range["end"].line + same_line) - cursor[1] + 1,
86+
col = 0,
87+
style = "minimal",
88+
border = "none",
89+
})
90+
vim.wo[preview_winnr].number = false
91+
vim.wo[preview_winnr].winhighlight = "Normal:NesAdd"
92+
vim.wo[preview_winnr].winblend = 0
93+
94+
ui.preview_winnr = preview_winnr
95+
end
96+
97+
suggestion.ui = ui
98+
99+
vim.b[bufnr].nes_state = suggestion
100+
101+
vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
102+
buffer = bufnr,
103+
callback = function()
104+
if not vim.b.nes_state then
105+
return true
106+
end
107+
108+
local accepted_cursor = vim.b.nes_state.accepted_cursor
109+
if accepted_cursor then
110+
local cursor = vim.api.nvim_win_get_cursor(win_id)
111+
if cursor[1] == accepted_cursor[1] and cursor[2] == accepted_cursor[2] then
112+
return
113+
end
114+
end
115+
116+
M.clear_suggestion(bufnr, ns_id)
117+
return true
118+
end,
119+
})
120+
end
121+
122+
return M

lua/copilot-lsp/types.lua

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
---@class copilotInlineEdit
1+
---@class copilotlsp.InlineEdit
22
---@field command lsp.Command
33
---@field range lsp.Range
44
---@field text string
5+
---@field newText string
56
---@field textDocument lsp.VersionedTextDocumentIdentifier
7+
---@field ui nes.EditSuggestionUI?
68

7-
---@class copilotInlineEditResponse
8-
---@field edits copilotInlineEdit[]
9+
---@class copilotlsp.copilotInlineEditResponse
10+
---@field edits copilotlsp.InlineEdit[]
11+
12+
---@class nes.EditSuggestionUI
13+
---@field preview_winnr? integer
14+
15+
---@class nes.Apply.Opts
16+
---@field jump? boolean | { hl_timeout: integer? } auto jump to the end of the new edit
17+
---@field trigger? boolean auto trigger the next edit suggestion

lua/copilot-lsp/util.lua

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
11
local M = {}
2-
---@param edit copilotInlineEdit
2+
---@param edit copilotlsp.InlineEdit
33
function M.apply_inline_edit(edit)
44
local bufnr = vim.uri_to_bufnr(edit.textDocument.uri)
5-
local multi_line
6-
if edit.text:match("\n") then
7-
multi_line = vim.split(edit.text, "\n")
8-
end
95

10-
vim.api.nvim_buf_set_text(
11-
bufnr,
12-
edit.range.start.line,
13-
edit.range.start.character,
14-
edit.range["end"].line,
15-
edit.range["end"].character,
16-
multi_line or { edit.text }
17-
)
6+
---@diagnostic disable-next-line: assign-type-mismatch
7+
vim.lsp.util.apply_text_edits({ edit }, bufnr, "utf-8")
188
end
199

2010
return M

0 commit comments

Comments
 (0)