Skip to content

Commit 625d5c6

Browse files
committed
feat(ui): define preview for inline edits
1 parent f30ec6b commit 625d5c6

File tree

2 files changed

+121
-2
lines changed

2 files changed

+121
-2
lines changed

lua/copilot-lsp/nes/init.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ local function handle_nes_response(err, result)
2121
end
2222

2323
--- Requests the NextEditSuggestion from the current cursor position
24-
---@param copilot_lss vim.lsp.Client?
24+
---@param copilot_lss? vim.lsp.Client|string
2525
function M.request_nes(copilot_lss)
2626
local pos_params = vim.lsp.util.make_position_params(0, "utf-16")
2727
local version = vim.lsp.util.buf_versions[vim.api.nvim_get_current_buf()]
28+
if type(copilot_lss) == "string" then
29+
copilot_lss = vim.lsp.get_clients({ name = copilot_lss })[1]
30+
end
2831
assert(copilot_lss, errs.ErrNotStarted)
2932
---@diagnostic disable-next-line: inject-field
3033
pos_params.textDocument.version = version

lua/copilot-lsp/nes/ui.lua

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,122 @@ 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+
107+
---@param bufnr integer
108+
---@param edit lsp.TextEdit
109+
---@return InlineEditPreview
110+
function M.preview_inline_edit(bufnr, edit)
111+
local text = edit.newText
112+
local range = edit.range
113+
local start_line = range.start.line
114+
local start_char = range.start.character
115+
local end_line = range["end"].line
116+
local end_char = range["end"].character
117+
118+
-- Split text by newline. Use plain=true to handle trailing newline correctly.
119+
local new_lines = vim.split(text, "\n", { plain = true })
120+
local num_new_lines = #new_lines
121+
122+
local old_lines = vim.api.nvim_buf_get_lines(bufnr, start_line, end_line + 1, false)
123+
local num_old_lines = #old_lines
124+
125+
local is_same_line = start_line == end_line
126+
local is_deletion = text == ""
127+
local is_insertion = is_same_line and start_char == end_char
128+
129+
if is_deletion and is_insertion then
130+
-- no-op
131+
return {}
132+
end
133+
134+
if is_same_line and is_deletion then
135+
-- inline deletion
136+
return {
137+
deletions = {
138+
{
139+
range = edit.range,
140+
},
141+
},
142+
}
143+
end
144+
145+
if is_insertion and num_new_lines == 1 and text ~= "" then
146+
-- inline insertion
147+
return {
148+
inline_insertion = {
149+
text = text,
150+
line = start_line,
151+
character = start_char,
152+
},
153+
}
154+
end
155+
156+
if is_insertion and num_new_lines > 1 then
157+
if start_char == #old_lines[1] and new_lines[1] == "" then
158+
-- insert lines after the start line
159+
return {
160+
line_insertion = {
161+
text = table.concat(vim.list_slice(new_lines, 2), "\n"),
162+
line = start_line,
163+
},
164+
}
165+
end
166+
167+
if end_char == 0 and new_lines[num_new_lines] == "" then
168+
-- insert lines before the end line
169+
return {
170+
lines_insertion = {
171+
text = table.concat(vim.list_slice(new_lines, 1, num_new_lines - 1), "\n"),
172+
line = start_line,
173+
},
174+
}
175+
end
176+
end
177+
178+
-- insert lines in the middle
179+
local prefix = old_lines[1]:sub(1, start_char)
180+
local suffix = old_lines[num_old_lines]:sub(end_char + 1)
181+
local new_lines_extend = vim.deepcopy(new_lines)
182+
new_lines_extend[1] = prefix .. new_lines_extend[1]
183+
new_lines_extend[num_new_lines] = new_lines_extend[num_new_lines] .. suffix
184+
local insertion = table.concat(new_lines_extend, "\n")
185+
186+
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+
},
193+
},
194+
},
195+
lines_insertion = {
196+
text = insertion,
197+
line = end_line,
198+
},
199+
}
200+
end
201+
86202
---@private
87203
---@param edits copilotlsp.InlineEdit[]
88204
---@param ns_id integer
@@ -103,11 +219,11 @@ function M._display_next_suggestion(edits, ns_id)
103219
local lines = M._calculate_lines(suggestion)
104220

105221
local line_replacement = false
222+
-- check if the edit is a inline insert or delete but not a whole line replacement
106223
if lines.same_line then
107224
local row = suggestion.range.start.line
108225
local start_col = suggestion.range.start.character
109226
local end_col = suggestion.range["end"].character
110-
111227
local line_text = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1]
112228
if start_col == 0 and end_col == #line_text then
113229
line_replacement = true

0 commit comments

Comments
 (0)