Skip to content

Commit 2335272

Browse files
committed
feat(suggestion): use separate context per buffer
1 parent d11e181 commit 2335272

File tree

1 file changed

+111
-80
lines changed

1 file changed

+111
-80
lines changed

lua/copilot/suggestion.lua

Lines changed: 111 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ local util = require("copilot.util")
66

77
local mod = {}
88

9+
---@alias copilot_suggestion_context { first?: integer, cycling?: integer, cycling_callbacks?: (fun(ctx: copilot_suggestion_context):nil)[], params?: table, suggestions?: copilot_get_completions_data_completion[], choice?: integer }
10+
911
local copilot = {
1012
setup_done = false,
1113

@@ -14,15 +16,7 @@ local copilot = {
1416
extmark_id = 1,
1517

1618
_copilot_timer = nil,
17-
_copilot = {
18-
first = nil,
19-
cycling = nil,
20-
cycling_callbacks = nil,
21-
params = nil,
22-
---@type copilot_get_completions_data_completion[]|nil
23-
suggestions = nil,
24-
choice = nil,
25-
},
19+
context = {},
2620

2721
auto_trigger = false,
2822
debounce = 75,
@@ -46,15 +40,26 @@ local function should_auto_trigger()
4640
return vim.b.copilot_suggestion_auto_trigger
4741
end
4842

49-
local function reset_state()
50-
copilot._copilot = {
51-
first = nil,
52-
cycling = nil,
53-
cycling_callbacks = nil,
54-
params = nil,
55-
suggestions = nil,
56-
choice = nil,
57-
}
43+
---@param bufnr? integer
44+
---@return copilot_suggestion_context
45+
local function get_ctx(bufnr)
46+
bufnr = bufnr or vim.api.nvim_get_current_buf()
47+
local ctx = copilot.context[bufnr]
48+
if not ctx then
49+
ctx = {}
50+
copilot.context[bufnr] = ctx
51+
end
52+
return ctx
53+
end
54+
55+
---@param ctx copilot_suggestion_context
56+
local function reset_ctx(ctx)
57+
ctx.first = nil
58+
ctx.cycling = nil
59+
ctx.cycling_callbacks = nil
60+
ctx.params = nil
61+
ctx.suggestions = nil
62+
ctx.choice = nil
5863
end
5964

6065
local function set_keymap(keymap)
@@ -153,15 +158,18 @@ local function reject(bufnr)
153158
end
154159
end
155160

156-
local function cancel_inflight_requests()
161+
---@param ctx? copilot_suggestion_context
162+
local function cancel_inflight_requests(ctx)
163+
ctx = ctx or get_ctx()
164+
157165
with_client(function(client)
158-
if copilot._copilot.first then
159-
client.cancel_request(copilot._copilot.first)
160-
copilot._copilot.first = nil
166+
if ctx.first then
167+
client.cancel_request(ctx.first)
168+
ctx.first = nil
161169
end
162-
if copilot._copilot.cycling then
163-
client.cancel_request(copilot._copilot.cycling)
164-
copilot._copilot.cycling = nil
170+
if ctx.cycling then
171+
client.cancel_request(ctx.cycling)
172+
ctx.cycling = nil
165173
end
166174
end)
167175
end
@@ -170,20 +178,23 @@ local function clear_preview()
170178
vim.api.nvim_buf_del_extmark(0, copilot.ns_id, copilot.extmark_id)
171179
end
172180

181+
---@param ctx? copilot_suggestion_context
173182
---@return copilot_get_completions_data_completion|nil
174-
local function get_current_suggestion()
183+
local function get_current_suggestion(ctx)
184+
ctx = ctx or get_ctx()
185+
175186
local ok, choice = pcall(function()
176187
if
177188
not vim.fn.mode():match("^[iR]")
178189
or vim.fn.pumvisible() == 1
179190
or vim.b.copilot_suggestion_hidden
180-
or not copilot._copilot.suggestions
181-
or #copilot._copilot.suggestions == 0
191+
or not ctx.suggestions
192+
or #ctx.suggestions == 0
182193
then
183194
return nil
184195
end
185196

186-
local choice = copilot._copilot.suggestions[copilot._copilot.choice]
197+
local choice = ctx.suggestions[ctx.choice]
187198
if not choice or not choice.range or choice.range.start.line ~= vim.fn.line(".") - 1 then
188199
return nil
189200
end
@@ -203,8 +214,11 @@ local function get_current_suggestion()
203214
return nil
204215
end
205216

206-
local function update_preview()
207-
local suggestion = get_current_suggestion()
217+
---@param ctx? copilot_suggestion_context
218+
local function update_preview(ctx)
219+
ctx = ctx or get_ctx()
220+
221+
local suggestion = get_current_suggestion(ctx)
208222
local displayLines = suggestion and vim.split(suggestion.displayText, "\n", { plain = true }) or {}
209223

210224
clear_preview()
@@ -216,10 +230,10 @@ local function update_preview()
216230
---@todo support popup preview
217231

218232
local annot = ""
219-
if copilot._copilot.cycling_callbacks then
233+
if ctx.cycling_callbacks then
220234
annot = "(1/…)"
221-
elseif copilot._copilot.cycling then
222-
annot = "(" .. copilot._copilot.choice .. "/" .. #copilot._copilot.suggestions .. ")"
235+
elseif ctx.cycling then
236+
annot = "(" .. ctx.choice .. "/" .. #ctx.suggestions .. ")"
223237
end
224238

225239
local cursor_col = vim.fn.col(".")
@@ -259,23 +273,27 @@ local function update_preview()
259273
end
260274
end
261275

262-
local function clear()
276+
---@param ctx? copilot_suggestion_context
277+
local function clear(ctx)
278+
ctx = ctx or get_ctx()
263279
stop_timer()
264-
cancel_inflight_requests()
265-
update_preview()
266-
reset_state()
280+
cancel_inflight_requests(ctx)
281+
update_preview(ctx)
282+
reset_ctx(ctx)
267283
end
268284

269285
---@param callback fun(err: any|nil, data: copilot_get_completions_data): nil
270286
local function complete(callback)
271287
stop_timer()
272288

289+
local ctx = get_ctx()
273290
local params = util.get_doc_params()
274291

275-
if not vim.deep_equal(copilot._copilot.params, params) then
292+
if not vim.deep_equal(ctx.params, params) then
276293
with_client(function(client)
277294
local _, id = api.get_completions(client, params, callback)
278-
copilot._copilot = { params = params, first = id }
295+
ctx.params = params
296+
ctx.first = id --[[@as integer]]
279297
end)
280298
end
281299
end
@@ -285,8 +303,9 @@ local function handle_trigger_request(err, data)
285303
if err then
286304
print(err)
287305
end
288-
copilot._copilot.suggestions = data and data.completions or {}
289-
copilot._copilot.choice = 1
306+
local ctx = get_ctx()
307+
ctx.suggestions = data and data.completions or {}
308+
ctx.choice = 1
290309
update_preview()
291310
end
292311

@@ -301,71 +320,74 @@ local function trigger(bufnr, timer)
301320
complete(handle_trigger_request)
302321
end
303322

304-
local function get_suggestions_cycling_callback(state, err, data)
305-
local callbacks = state.cycling_callbacks
306-
state.cycling_callbacks = nil
323+
---@param ctx copilot_suggestion_context
324+
local function get_suggestions_cycling_callback(ctx, err, data)
325+
local callbacks = ctx.cycling_callbacks or {}
326+
ctx.cycling_callbacks = nil
307327

308328
if err then
309329
print(err)
310330
return
311331
end
312332

313-
if not state.suggestions then
333+
if not ctx.suggestions then
314334
return
315335
end
316336

317337
local seen = {}
318338

319-
for _, suggestion in ipairs(state.suggestions) do
339+
for _, suggestion in ipairs(ctx.suggestions) do
320340
seen[suggestion.text] = true
321341
end
322342

323343
for _, suggestion in ipairs(data.completions or {}) do
324344
if not seen[suggestion.text] then
325-
table.insert(state.suggestions, suggestion)
345+
table.insert(ctx.suggestions, suggestion)
326346
seen[suggestion.text] = true
327347
end
328348
end
329349

330350
for _, callback in ipairs(callbacks) do
331-
callback(state)
351+
callback(ctx)
332352
end
333353
end
334354

335-
local function get_suggestions_cycling(callback)
336-
if copilot._copilot.cycling_callbacks then
337-
table.insert(copilot._copilot.cycling_callbacks, callback)
355+
---@param callback fun(ctx: copilot_suggestion_context): nil
356+
---@param ctx copilot_suggestion_context
357+
local function get_suggestions_cycling(callback, ctx)
358+
if ctx.cycling_callbacks then
359+
table.insert(ctx.cycling_callbacks, callback)
338360
return
339361
end
340362

341-
if copilot._copilot.cycling then
342-
callback(copilot._copilot)
363+
if ctx.cycling then
364+
callback(ctx)
343365
return
344366
end
345367

346-
if copilot._copilot.suggestions then
347-
copilot._copilot.cycling_callbacks = { callback }
368+
if ctx.suggestions then
369+
ctx.cycling_callbacks = { callback }
348370
with_client(function(client)
349-
local _, id = api.get_completions_cycling(client, copilot._copilot.params, function(err, data)
350-
get_suggestions_cycling_callback(copilot._copilot, err, data)
371+
local _, id = api.get_completions_cycling(client, ctx.params, function(err, data)
372+
get_suggestions_cycling_callback(ctx, err, data)
351373
end)
352-
copilot._copilot.cycling = id
353-
update_preview()
374+
ctx.cycling = id --[[@as integer]]
375+
update_preview(ctx)
354376
end)
355377
end
356378
end
357379

358-
local function advance(count, state)
359-
if state ~= copilot._copilot then
380+
local function advance(count, ctx)
381+
if ctx ~= get_ctx() then
360382
return
361383
end
362384

363-
state.choice = (state.choice + count) % #state.suggestions
364-
if state.choice < 1 then
365-
state.choice = #state.suggestions
385+
ctx.choice = (ctx.choice + count) % #ctx.suggestions
386+
if ctx.choice < 1 then
387+
ctx.choice = #ctx.suggestions
366388
end
367389

368-
update_preview()
390+
update_preview(ctx)
369391
end
370392

371393
local function schedule()
@@ -382,38 +404,44 @@ local function schedule()
382404
end
383405

384406
function mod.next()
407+
local ctx = get_ctx()
408+
385409
-- no suggestion request yet
386-
if not copilot._copilot.first then
410+
if not ctx.first then
387411
schedule()
388412
return
389413
end
390414

391-
get_suggestions_cycling(function(state)
392-
advance(1, state)
393-
end)
415+
get_suggestions_cycling(function(context)
416+
advance(1, context)
417+
end, ctx)
394418
end
395419

396420
function mod.prev()
421+
local ctx = get_ctx()
422+
397423
-- no suggestion request yet
398-
if not copilot._copilot.first then
424+
if not ctx.first then
399425
schedule()
400426
return
401427
end
402428

403-
get_suggestions_cycling(function(state)
404-
advance(-1, state)
405-
end)
429+
get_suggestions_cycling(function(context)
430+
advance(-1, context)
431+
end, ctx)
406432
end
407433

408434
---@param modifier? (fun(suggestion: copilot_get_completions_data_completion): copilot_get_completions_data_completion)
409435
function mod.accept(modifier)
410-
local suggestion = get_current_suggestion()
436+
local ctx = get_ctx()
437+
438+
local suggestion = get_current_suggestion(ctx)
411439
if not suggestion or vim.fn.empty(suggestion.text) == 1 then
412440
return
413441
end
414442

415-
cancel_inflight_requests()
416-
reset_state()
443+
cancel_inflight_requests(ctx)
444+
reset_ctx(ctx)
417445

418446
vim.api.nvim_buf_set_var(0, "_copilot_uuid", "")
419447
with_client(function(client)
@@ -477,9 +505,10 @@ function mod.accept_line()
477505
end
478506

479507
function mod.dismiss()
508+
local ctx = get_ctx()
480509
reject(0)
481-
clear()
482-
update_preview()
510+
clear(ctx)
511+
update_preview(ctx)
483512
end
484513

485514
function mod.is_visible()
@@ -514,7 +543,8 @@ local function on_buf_enter()
514543
end
515544

516545
local function on_cursor_moved_i()
517-
if copilot._copilot_timer or copilot._copilot.params or should_auto_trigger() then
546+
local ctx = get_ctx()
547+
if copilot._copilot_timer or ctx.params or should_auto_trigger() then
518548
schedule()
519549
end
520550
end
@@ -526,6 +556,7 @@ end
526556
---@param info { buf: integer }
527557
local function on_buf_unload(info)
528558
reject(info.buf)
559+
copilot.context[info.buf] = nil
529560
end
530561

531562
local function on_vim_leave_pre()

0 commit comments

Comments
 (0)