Skip to content

Commit e4b9361

Browse files
authored
fix(nes): rendering edge cases and tests (#9)
* tests: add NES UI tests * fix(nes): edge case around deletion only * feat(tests): add signin tests and screenshots Add signin fixture, screenshot files, and integration test for the Copilot LSP sign in modal. The test simulates the device flow with code verification. * feat(nes): support add only edits Implement trailing whitespace trimming to handle add only edits and update UI calculations for proper row placement. Adjust tests, mocks, and screenshots to reflect the new behavior.
1 parent e4293f3 commit e4b9361

25 files changed

+1059
-14
lines changed

lua/copilot-lsp/handlers.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ local function sign_in(client_id, bufnr)
111111
client:request(methods.signIn, vim.empty_dict(), nil, bufnr)
112112
end
113113

114-
---@param res {busy: boolean, kind: 'Normal'|'Error'|'Warning'|'Incative', message: string}
114+
---@param res {busy: boolean, kind: 'Normal'|'Error'|'Warning'|'Inactive', message: string}
115115
M["didChangeStatus"] = function(err, res, ctx)
116116
if err then
117117
return

lua/copilot-lsp/nes/ui.lua

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@ function M.clear_suggestion(bufnr, ns_id)
2323
vim.b[bufnr].nes_state = nil
2424
end
2525

26+
local function trim_end(s)
27+
return s:gsub("%s+$", "")
28+
end
29+
2630
---@private
2731
---@param suggestion copilotlsp.InlineEdit
2832
---@return nes.LineCalculationResult
2933
function M._calculate_lines(suggestion)
3034
local deleted_lines_count = suggestion.range["end"].line - suggestion.range.start.line
31-
local added_lines = vim.split(suggestion.newText, "\n")
35+
local added_lines = vim.split(trim_end(suggestion.newText), "\n")
3236
local added_lines_count = suggestion.newText == "" and 0 or #added_lines
3337
local same_line = 0
3438

@@ -38,25 +42,40 @@ function M._calculate_lines(suggestion)
3842
same_line = 1
3943
end
4044

45+
-- if
46+
-- suggestion.range.start.line == suggestion.range["end"].line
47+
-- and suggestion.range.start.character == suggestion.range["end"].character
48+
-- then
49+
-- --add only
50+
-- TODO: Do we need to position specifically for add only?
51+
-- UI tests seem to say no
52+
-- end
53+
4154
-- Calculate positions for delete highlight extmark
4255
---@type nes.DeleteExtmark
4356
local delete_extmark = {
4457
row = suggestion.range.start.line,
45-
end_row = suggestion.range["end"].line + 1,
58+
end_row = (
59+
suggestion.range["end"].character ~= 0 and suggestion.range["end"].line + 1
60+
or suggestion.range["end"].line
61+
),
4662
}
4763

4864
-- Calculate positions for virtual lines extmark
4965
---@type nes.VirtLinesExtmark
5066
local virt_lines_extmark = {
51-
row = suggestion.range["end"].line,
67+
row = (
68+
suggestion.range["end"].character ~= 0 and suggestion.range["end"].line
69+
or suggestion.range["end"].line - 1
70+
),
5271
virt_lines_count = added_lines_count,
5372
}
5473

5574
-- Calculate positions for floating window
5675
---@type nes.FloatWin
5776
local float_win = {
5877
height = #added_lines,
59-
row = suggestion.range["end"].line + deleted_lines_count + 1,
78+
row = suggestion.range["end"].line + deleted_lines_count + (suggestion.range["end"].character ~= 0 and 1 or 0),
6079
}
6180

6281
return {

tests/fixtures/addonly_edit.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
1 line
2+
2 line
3+
4 line

tests/fixtures/multiline_edit.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
line one
2+
line two
3+
line three

tests/fixtures/removal_edit.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
line one
2+
line two
3+
line three

tests/fixtures/sameline_edit.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
aaa
2+
bbb
3+
ccc

tests/fixtures/signin.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Sign In Test

tests/mock_lsp.lua

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
local M = {}
2+
3+
M.messages = {}
4+
5+
---@param td lsp.VersionedTextDocumentIdentifier
6+
---@return copilotlsp.copilotInlineEditResponse
7+
local function getNesResponse(td)
8+
local filename = vim.fs.basename(vim.uri_to_fname(td.uri))
9+
---@type table<string, copilotlsp.copilotInlineEditResponse>
10+
local responses = {
11+
["sameline_edit.txt"] = {
12+
edits = {
13+
{
14+
command = { title = "mock", command = "mock" },
15+
range = {
16+
start = { line = 0, character = 0 },
17+
["end"] = { line = 0, character = 3 },
18+
},
19+
textDocument = td,
20+
text = "xyz",
21+
---@diagnostic disable-next-line: assign-type-mismatch
22+
newText = nil,
23+
},
24+
},
25+
},
26+
["multiline_edit.txt"] = {
27+
edits = {
28+
{
29+
command = { title = "mock", command = "mock" },
30+
range = {
31+
start = { line = 0, character = 0 },
32+
["end"] = { line = 1, character = 8 },
33+
},
34+
textDocument = td,
35+
text = "new line one\nnew line two",
36+
---@diagnostic disable-next-line: assign-type-mismatch
37+
newText = nil,
38+
},
39+
},
40+
},
41+
["removal_edit.txt"] = {
42+
edits = {
43+
{
44+
command = { title = "mock", command = "mock" },
45+
range = {
46+
start = { line = 1, character = 0 },
47+
["end"] = { line = 2, character = 0 },
48+
},
49+
textDocument = td,
50+
text = "",
51+
---@diagnostic disable-next-line: assign-type-mismatch
52+
newText = nil,
53+
},
54+
},
55+
},
56+
["addonly_edit.txt"] = {
57+
edits = {
58+
{
59+
command = { title = "mock", command = "mock" },
60+
range = {
61+
start = { line = 2, character = 0 },
62+
["end"] = { line = 2, character = 0 },
63+
},
64+
textDocument = td,
65+
text = "line 3\n",
66+
---@diagnostic disable-next-line: assign-type-mismatch
67+
newText = nil,
68+
},
69+
},
70+
},
71+
}
72+
local response = responses[filename]
73+
assert(response, "unhandled doc")
74+
return response
75+
end
76+
77+
function M.server()
78+
local closing = false
79+
local srv = {}
80+
81+
function srv.request(method, params, handler)
82+
table.insert(M.messages, { method = method, params = params })
83+
if method == "initialize" then
84+
handler(nil, {
85+
capabilities = {},
86+
})
87+
elseif method == "shutdown" then
88+
handler(nil, nil)
89+
elseif method == "textDocument/copilotInlineEdit" then
90+
local response = getNesResponse(params.textDocument)
91+
handler(nil, response)
92+
else
93+
assert(false, "Unhandled method: " .. method)
94+
end
95+
end
96+
97+
function srv.notify(method, params)
98+
table.insert(M.messages, { method = method, params = params })
99+
if method == "exit" then
100+
closing = true
101+
end
102+
end
103+
104+
function srv.is_closing()
105+
return closing
106+
end
107+
108+
function srv.terminate()
109+
closing = true
110+
end
111+
112+
return srv
113+
end
114+
115+
function M.Reset()
116+
M.messages = {}
117+
end
118+
119+
return M

tests/nes/test_nes.lua

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
local eq = MiniTest.expect.equality
2+
local ref = MiniTest.expect.reference_screenshot
3+
4+
local child = MiniTest.new_child_neovim()
5+
6+
local T = MiniTest.new_set()
7+
T["nes"] = MiniTest.new_set({
8+
hooks = {
9+
pre_case = function()
10+
child.restart({ "-u", "scripts/minimal_init.lua" })
11+
child.lua_func(function()
12+
vim.lsp.config("copilot", {
13+
cmd = require("tests.mock_lsp").server,
14+
})
15+
vim.lsp.enable("copilot")
16+
end)
17+
end,
18+
post_once = child.stop,
19+
},
20+
})
21+
22+
T["nes"]["same line edit"] = function()
23+
child.cmd("edit tests/fixtures/sameline_edit.txt")
24+
ref(child.get_screenshot())
25+
vim.uv.sleep(500)
26+
local lsp_name = child.lua_func(function()
27+
return vim.lsp.get_clients()[1].name
28+
end)
29+
eq(lsp_name, "copilot")
30+
child.lua_func(function()
31+
local copilot = vim.lsp.get_clients()[1]
32+
require("copilot-lsp.nes").request_nes(copilot)
33+
end)
34+
vim.uv.sleep(500)
35+
ref(child.get_screenshot())
36+
child.lua_func(function()
37+
require("copilot-lsp.nes").apply_pending_nes(0)
38+
end)
39+
ref(child.get_screenshot())
40+
end
41+
42+
T["nes"]["multi line edit"] = function()
43+
child.cmd("edit tests/fixtures/multiline_edit.txt")
44+
ref(child.get_screenshot())
45+
vim.uv.sleep(500)
46+
local lsp_name = child.lua_func(function()
47+
return vim.lsp.get_clients()[1].name
48+
end)
49+
eq(lsp_name, "copilot")
50+
child.lua_func(function()
51+
local copilot = vim.lsp.get_clients()[1]
52+
require("copilot-lsp.nes").request_nes(copilot)
53+
end)
54+
vim.uv.sleep(500)
55+
ref(child.get_screenshot())
56+
child.lua_func(function()
57+
require("copilot-lsp.nes").apply_pending_nes(0)
58+
end)
59+
ref(child.get_screenshot())
60+
end
61+
62+
T["nes"]["removal edit"] = function()
63+
child.cmd("edit tests/fixtures/removal_edit.txt")
64+
ref(child.get_screenshot())
65+
vim.uv.sleep(500)
66+
local lsp_name = child.lua_func(function()
67+
return vim.lsp.get_clients()[1].name
68+
end)
69+
eq(lsp_name, "copilot")
70+
child.lua_func(function()
71+
local copilot = vim.lsp.get_clients()[1]
72+
require("copilot-lsp.nes").request_nes(copilot)
73+
end)
74+
vim.uv.sleep(500)
75+
ref(child.get_screenshot())
76+
child.lua_func(function()
77+
require("copilot-lsp.nes").apply_pending_nes(0)
78+
end)
79+
ref(child.get_screenshot())
80+
end
81+
82+
T["nes"]["add only edit"] = function()
83+
child.cmd("edit tests/fixtures/addonly_edit.txt")
84+
ref(child.get_screenshot())
85+
vim.uv.sleep(500)
86+
local lsp_name = child.lua_func(function()
87+
return vim.lsp.get_clients()[1].name
88+
end)
89+
eq(lsp_name, "copilot")
90+
child.lua_func(function()
91+
local copilot = vim.lsp.get_clients()[1]
92+
require("copilot-lsp.nes").request_nes(copilot)
93+
end)
94+
vim.uv.sleep(500)
95+
ref(child.get_screenshot())
96+
child.lua_func(function()
97+
require("copilot-lsp.nes").apply_pending_nes(0)
98+
end)
99+
ref(child.get_screenshot())
100+
end
101+
return T

0 commit comments

Comments
 (0)