Skip to content

Commit a6a4f3f

Browse files
committed
feat(ui): added preview
1 parent 625d5c6 commit a6a4f3f

15 files changed

+817
-92
lines changed

lua/copilot-lsp/nes/ui.lua

Lines changed: 46 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -83,31 +83,10 @@ function M._calculate_lines(suggestion)
8383
}
8484
end
8585

86-
---@private
87-
---@class TextDeletion
88-
---@field range lsp.Range
89-
90-
---@private
91-
---@class InlineInsertion
92-
---@field text string
93-
---@field line integer
94-
---@field character integer
95-
96-
---@private
97-
---@class TextInsertion
98-
---@field text string
99-
---@field line integer insert lines at this line
100-
101-
---@private
102-
---@class InlineEditPreview
103-
---@field deletions? TextDeletion[]
104-
---@field inline_insertion? InlineInsertion
105-
---@field lines_insertion? TextInsertion
106-
10786
---@param bufnr integer
10887
---@param edit lsp.TextEdit
109-
---@return InlineEditPreview
110-
function M.preview_inline_edit(bufnr, edit)
88+
---@return copilotlsp.nes.InlineEditPreview
89+
function M.caculate_preview(bufnr, edit)
11190
local text = edit.newText
11291
local range = edit.range
11392
local start_line = range.start.line
@@ -134,10 +113,8 @@ function M.preview_inline_edit(bufnr, edit)
134113
if is_same_line and is_deletion then
135114
-- inline deletion
136115
return {
137-
deletions = {
138-
{
139-
range = edit.range,
140-
},
116+
deletion = {
117+
range = edit.range,
141118
},
142119
}
143120
end
@@ -157,7 +134,7 @@ function M.preview_inline_edit(bufnr, edit)
157134
if start_char == #old_lines[1] and new_lines[1] == "" then
158135
-- insert lines after the start line
159136
return {
160-
line_insertion = {
137+
lines_insertion = {
161138
text = table.concat(vim.list_slice(new_lines, 2), "\n"),
162139
line = start_line,
163140
},
@@ -169,7 +146,7 @@ function M.preview_inline_edit(bufnr, edit)
169146
return {
170147
lines_insertion = {
171148
text = table.concat(vim.list_slice(new_lines, 1, num_new_lines - 1), "\n"),
172-
line = start_line,
149+
line = math.max(start_line - 1, 0),
173150
},
174151
}
175152
end
@@ -184,12 +161,10 @@ function M.preview_inline_edit(bufnr, edit)
184161
local insertion = table.concat(new_lines_extend, "\n")
185162

186163
return {
187-
deletions = {
188-
{
189-
range = {
190-
start = { line = start_line, character = 0 },
191-
["end"] = { line = end_line, character = #old_lines[num_old_lines] },
192-
},
164+
deletion = {
165+
range = {
166+
start = { line = start_line, character = 0 },
167+
["end"] = { line = end_line, character = #old_lines[num_old_lines] - 1 },
193168
},
194169
},
195170
lines_insertion = {
@@ -199,11 +174,44 @@ function M.preview_inline_edit(bufnr, edit)
199174
}
200175
end
201176

177+
---@param bufnr integer
178+
---@param ns_id integer
179+
---@param preview copilotlsp.nes.InlineEditPreview
180+
function M.display_inline_edit_preview(bufnr, ns_id, preview)
181+
if preview.deletion then
182+
local range = preview.deletion.range
183+
vim.api.nvim_buf_set_extmark(bufnr, ns_id, range.start.line, range.start.character, {
184+
hl_group = "CopilotLspNesDelete",
185+
end_row = range["end"].line,
186+
end_col = range["end"].character + 1,
187+
})
188+
end
189+
190+
local inline_insertion = preview.inline_insertion
191+
if inline_insertion then
192+
local virt_lines =
193+
require("copilot-lsp.util").hl_text_to_virt_lines(inline_insertion.text, vim.bo[bufnr].filetype)
194+
vim.api.nvim_buf_set_extmark(bufnr, ns_id, inline_insertion.line, inline_insertion.character, {
195+
virt_text = virt_lines[1],
196+
virt_text_pos = "inline",
197+
})
198+
end
199+
200+
local lines_insertion = preview.lines_insertion
201+
if lines_insertion then
202+
local virt_lines =
203+
require("copilot-lsp.util").hl_text_to_virt_lines(lines_insertion.text, vim.bo[bufnr].filetype)
204+
vim.api.nvim_buf_set_extmark(bufnr, ns_id, lines_insertion.line, 0, {
205+
virt_lines = virt_lines,
206+
})
207+
end
208+
end
209+
202210
---@private
203211
---@param edits copilotlsp.InlineEdit[]
204212
---@param ns_id integer
205213
function M._display_next_suggestion(edits, ns_id)
206-
local bufnr = vim.api.nvim_get_current_buf()
214+
local bufnr = vim.uri_to_bufnr(edits[1].textDocument.uri)
207215
local state = vim.b[bufnr].nes_state
208216
if state then
209217
M.clear_suggestion(vim.api.nvim_get_current_buf(), ns_id)
@@ -213,61 +221,10 @@ function M._display_next_suggestion(edits, ns_id)
213221
-- vim.notify("No suggestion available", vim.log.levels.INFO)
214222
return
215223
end
216-
local bufnr = vim.uri_to_bufnr(edits[1].textDocument.uri)
217224
local suggestion = edits[1]
218225

219-
local lines = M._calculate_lines(suggestion)
220-
221-
local line_replacement = false
222-
-- check if the edit is a inline insert or delete but not a whole line replacement
223-
if lines.same_line then
224-
local row = suggestion.range.start.line
225-
local start_col = suggestion.range.start.character
226-
local end_col = suggestion.range["end"].character
227-
local line_text = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1]
228-
if start_col == 0 and end_col == #line_text then
229-
line_replacement = true
230-
end
231-
end
232-
233-
if lines.same_line and not line_replacement then
234-
local row = suggestion.range.start.line
235-
local start_col = suggestion.range.start.character
236-
local end_col = suggestion.range["end"].character
237-
238-
-- inline edit
239-
if start_col < end_col then
240-
vim.api.nvim_buf_set_extmark(bufnr, ns_id, row, start_col, {
241-
hl_group = "CopilotLspNesDelete",
242-
end_col = end_col,
243-
})
244-
end
245-
if suggestion.text ~= "" then
246-
local virt_lines =
247-
require("copilot-lsp.util").hl_text_to_virt_lines(suggestion.text, vim.bo[bufnr].filetype)
248-
local virt_text = virt_lines[1]
249-
vim.api.nvim_buf_set_extmark(bufnr, ns_id, row, end_col, {
250-
virt_text = virt_text,
251-
virt_text_pos = "inline",
252-
})
253-
end
254-
else
255-
if lines.deleted_lines_count > 0 then
256-
-- Deleted range red highlight
257-
vim.api.nvim_buf_set_extmark(bufnr, ns_id, lines.delete_extmark.row, 0, {
258-
hl_group = "CopilotLspNesDelete",
259-
end_row = lines.delete_extmark.end_row,
260-
})
261-
end
262-
if lines.added_lines_count > 0 then
263-
local text = trim_end(edits[1].text)
264-
local virt_lines = require("copilot-lsp.util").hl_text_to_virt_lines(text, vim.bo[bufnr].filetype)
265-
266-
vim.api.nvim_buf_set_extmark(bufnr, ns_id, lines.virt_lines_extmark.row, 0, {
267-
virt_lines = virt_lines,
268-
})
269-
end
270-
end
226+
local preview = M.caculate_preview(bufnr, suggestion)
227+
M.display_inline_edit_preview(bufnr, ns_id, preview)
271228

272229
vim.b[bufnr].nes_state = suggestion
273230

lua/copilot-lsp/types.lua

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
---@class copilotlsp.InlineEdit
1+
---@class copilotlsp.InlineEdit : lsp.TextEdit
22
---@field command lsp.Command
3-
---@field range lsp.Range
43
---@field text string
5-
---@field newText string
64
---@field textDocument lsp.VersionedTextDocumentIdentifier
75

86
---@class copilotlsp.copilotInlineEditResponse
@@ -29,3 +27,20 @@
2927
---@field same_line boolean
3028
---@field delete_extmark copilotlsp.nes.DeleteExtmark
3129
---@field virt_lines_extmark copilotlsp.nes.AddExtmark
30+
31+
---@class copilotlsp.nes.TextDeletion
32+
---@field range lsp.Range
33+
34+
---@class copilotlsp.nes.InlineInsertion
35+
---@field text string
36+
---@field line integer
37+
---@field character integer
38+
39+
---@class copilotlsp.nes.TextInsertion
40+
---@field text string
41+
---@field line integer insert lines at this line
42+
43+
---@class copilotlsp.nes.InlineEditPreview
44+
---@field deletion? copilotlsp.nes.TextDeletion
45+
---@field inline_insertion? copilotlsp.nes.InlineInsertion
46+
---@field lines_insertion? copilotlsp.nes.TextInsertion

tests/nes/test_ui_preview.lua

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
local ref = MiniTest.expect.reference_screenshot
2+
3+
local child = MiniTest.new_child_neovim()
4+
5+
local T = MiniTest.new_set()
6+
T["ui_preview"] = MiniTest.new_set({
7+
hooks = {
8+
pre_case = function()
9+
child.restart({ "-u", "scripts/minimal_init.lua" })
10+
child.api.nvim_set_hl(0, "CopilotLspNesAdd", { link = "DiffAdd", default = true })
11+
child.api.nvim_set_hl(0, "CopilotLspNesDelete", { link = "DiffDelete", default = true })
12+
child.api.nvim_set_hl(0, "CopilotLspNesApply", { link = "DiffText", default = true })
13+
child.bo.filetype = "txt"
14+
end,
15+
post_once = child.stop,
16+
},
17+
})
18+
19+
local cases = {
20+
["inline insertion"] = {
21+
content = "123456\nabcdefg\nhijklmn",
22+
edit = {
23+
range = {
24+
start = {
25+
line = 1,
26+
character = 2,
27+
},
28+
["end"] = {
29+
line = 1,
30+
character = 2,
31+
},
32+
},
33+
newText = "XYZ",
34+
},
35+
},
36+
["inline deletion"] = {
37+
content = "123456\nabcdefg\nhijklmn",
38+
edit = {
39+
range = {
40+
start = {
41+
line = 1,
42+
character = 2,
43+
},
44+
["end"] = {
45+
line = 1,
46+
character = 5,
47+
},
48+
},
49+
newText = "",
50+
},
51+
},
52+
["insert lines after line end"] = {
53+
content = "123456\nabcdefg\nhijklmn",
54+
edit = {
55+
range = {
56+
start = {
57+
line = 1,
58+
character = 7,
59+
},
60+
["end"] = {
61+
line = 1,
62+
character = 7,
63+
},
64+
},
65+
newText = "\nXXXX\nYYY",
66+
},
67+
},
68+
["insert lines at the beginning"] = {
69+
content = "123456\nabcdefg\nhijklmn",
70+
edit = {
71+
range = {
72+
start = {
73+
line = 1,
74+
character = 0,
75+
},
76+
["end"] = {
77+
line = 1,
78+
character = 0,
79+
},
80+
},
81+
newText = "XXXX\nYYY\n",
82+
},
83+
},
84+
["inline replacement"] = {
85+
content = "123456\nabcdefg\nhijklmn",
86+
edit = {
87+
range = {
88+
start = {
89+
line = 0,
90+
character = 3,
91+
},
92+
["end"] = {
93+
line = 1,
94+
character = 4,
95+
},
96+
},
97+
newText = "XXXX\nYYY",
98+
},
99+
},
100+
["single line replacement"] = {
101+
content = "123456\nabcdefg\nhijklmn",
102+
edit = {
103+
range = {
104+
start = {
105+
line = 1,
106+
character = 0,
107+
},
108+
["end"] = {
109+
line = 1,
110+
character = 8,
111+
},
112+
},
113+
newText = "XXXX",
114+
},
115+
},
116+
}
117+
118+
local function set_content(content)
119+
child.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(content, "\n", { plain = true }))
120+
end
121+
122+
do
123+
for name, case in pairs(cases) do
124+
T["ui_preview"][name] = function()
125+
set_content(case.content)
126+
ref(child.get_screenshot())
127+
128+
child.g.inline_edit = case.edit
129+
local _preview = child.lua_func(function()
130+
local ns_id = vim.api.nvim_create_namespace("nes")
131+
local bufnr = vim.api.nvim_get_current_buf()
132+
local preview = require("copilot-lsp.nes.ui").caculate_preview(bufnr, vim.g.inline_edit)
133+
require("copilot-lsp.nes.ui").display_inline_edit_preview(bufnr, ns_id, preview)
134+
return preview
135+
end)
136+
ref(child.get_screenshot())
137+
end
138+
end
139+
end
140+
141+
return T

0 commit comments

Comments
 (0)