@@ -9,7 +9,7 @@ local utils = require("copilot.client.utils")
9
9
10
10
local M = {}
11
11
12
- --- @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 , shown_choices ?: table<string , true> }
12
+ --- @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 , shown_choices ?: table<string , true> , accepted_partial ?: boolean }
13
13
14
14
local copilot = {
15
15
setup_done = false ,
@@ -26,6 +26,8 @@ local copilot = {
26
26
debounce = 75 ,
27
27
}
28
28
29
+ local ignore_next_cursor_moved = false
30
+
29
31
local function with_client (fn )
30
32
local client = c .get ()
31
33
if client then
@@ -58,6 +60,28 @@ local function get_ctx(bufnr)
58
60
return ctx
59
61
end
60
62
63
+ --- @param idx integer
64
+ --- @param new_line integer
65
+ --- @param new_end_col integer
66
+ --- @param bufnr ? integer
67
+ local function update_ctx_suggestion_position (idx , new_line , new_end_col , bufnr )
68
+ bufnr = bufnr or vim .api .nvim_get_current_buf ()
69
+
70
+ if not copilot .context [bufnr ] then
71
+ return
72
+ end
73
+
74
+ if not copilot .context [bufnr ].suggestions [idx ] then
75
+ return
76
+ end
77
+
78
+ local suggestion = copilot .context [bufnr ].suggestions [idx ]
79
+ suggestion .range [" start" ].line = new_line
80
+ suggestion .range [" start" ].character = 0
81
+ suggestion .range [" end" ].line = new_line
82
+ suggestion .range [" end" ].character = new_end_col
83
+ end
84
+
61
85
--- @param idx integer
62
86
--- @param text string
63
87
--- @param bufnr ? integer
@@ -89,6 +113,7 @@ local function reset_ctx(ctx)
89
113
ctx .suggestions = nil
90
114
ctx .choice = nil
91
115
ctx .shown_choices = nil
116
+ ctx .accepted_partial = nil
92
117
end
93
118
94
119
local function set_keymap (keymap )
@@ -249,10 +274,10 @@ local function get_current_suggestion(ctx)
249
274
return nil
250
275
end
251
276
252
- if choice .range .start .character ~= 0 then
253
- -- unexpected range
254
- return nil
255
- end
277
+ -- if choice.range.start.character ~= 0 then
278
+ -- -- unexpected range
279
+ -- return nil
280
+ -- end
256
281
257
282
return choice
258
283
end )
@@ -475,7 +500,7 @@ local function schedule(ctx)
475
500
stop_timer ()
476
501
end
477
502
478
- update_preview (ctx )
503
+ -- update_preview(ctx)
479
504
local bufnr = vim .api .nvim_get_current_buf ()
480
505
copilot ._copilot_timer = vim .fn .timer_start (copilot .debounce , function (timer )
481
506
logger .trace (" suggestion schedule timer" , bufnr )
@@ -487,6 +512,10 @@ function M.next()
487
512
local ctx = get_ctx ()
488
513
logger .trace (" suggestion next" , ctx )
489
514
515
+ if ctx .accepted_partial then
516
+ reset_ctx (ctx )
517
+ end
518
+
490
519
-- no suggestion request yet
491
520
if not ctx .first then
492
521
logger .trace (" suggestion next, no first request" )
@@ -503,6 +532,10 @@ function M.prev()
503
532
local ctx = get_ctx ()
504
533
logger .trace (" suggestion prev" , ctx )
505
534
535
+ if ctx .accepted_partial then
536
+ reset_ctx (ctx )
537
+ end
538
+
506
539
-- no suggestion request yet
507
540
if not ctx .first then
508
541
logger .trace (" suggestion prev, no first request" , ctx )
@@ -532,13 +565,17 @@ function M.accept(modifier)
532
565
return
533
566
end
534
567
535
- cancel_inflight_requests (ctx )
536
- reset_ctx (ctx )
537
-
538
568
if type (modifier ) == " function" then
539
569
suggestion = modifier (suggestion )
540
570
end
541
571
572
+ local accepted_partial = suggestion .partial_text and suggestion .partial_text ~= " "
573
+
574
+ if not accepted_partial then
575
+ cancel_inflight_requests (ctx )
576
+ reset_ctx (ctx )
577
+ end
578
+
542
579
with_client (function (client )
543
580
local ok , _ = pcall (function ()
544
581
api .notify_accepted (
@@ -552,34 +589,62 @@ function M.accept(modifier)
552
589
end
553
590
end )
554
591
555
- clear_preview ()
592
+ local newText
593
+
594
+ if accepted_partial then
595
+ newText = suggestion .partial_text
596
+ ctx .accepted_partial = true
597
+ ignore_next_cursor_moved = true
598
+ else
599
+ clear_preview ()
600
+ newText = suggestion .text
601
+ end
556
602
557
- local range , newText = suggestion .range , suggestion . text
603
+ local range = suggestion .range
558
604
local cursor = vim .api .nvim_win_get_cursor (0 )
559
605
local line , character = cursor [1 ] - 1 , cursor [2 ]
560
606
if range [" end" ].line == line and range [" end" ].character < character then
561
607
range [" end" ].character = character
562
608
end
563
609
564
- -- Hack for 'autoindent', makes the indent persist. Check `:help 'autoindent'`.
565
610
vim .schedule_wrap (function ()
566
611
-- Create an undo breakpoint
567
612
vim .cmd (" let &undolevels=&undolevels" )
613
+ -- Hack for 'autoindent', makes the indent persist. Check `:help 'autoindent'`.
568
614
vim .api .nvim_feedkeys (vim .api .nvim_replace_termcodes (" <Space><Left><Del>" , true , false , true ), " n" , false )
569
615
local bufnr = vim .api .nvim_get_current_buf ()
616
+
570
617
local encoding = vim .api .nvim_get_option_value (" fileencoding" , { buf = bufnr }) ~= " "
571
618
and vim .api .nvim_get_option_value (" fileencoding" , { buf = bufnr })
572
619
or vim .api .nvim_get_option_value (" encoding" , { scope = " global" })
573
- vim .lsp .util .apply_text_edits ({ { range = range , newText = newText } }, bufnr , encoding )
574
620
575
- -- instead of calling <End>, go to the pos of the row after the last \n of inserted text
576
- -- local cursor_keys = string.rep("<Down>", #vim.split(newText, "\n", { plain = true }) - 1) .. "<End>"
577
- -- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(cursor_keys, true, false, true), "n", false)
578
621
local lines = vim .split (newText , " \n " , { plain = true })
579
- local last_line = lines [# lines ]
580
- local cursor_keys = string.rep (" <Down>" , # lines - 1 )
622
+ local lines_count = # lines
623
+ local last_col = # lines [lines_count ]
624
+
625
+ -- apply_text_edits will remove the last \n if the last line is empty,
626
+ -- so we trick it by adding an extra one
627
+ if last_col == 0 then
628
+ newText = newText .. " \n "
629
+ end
630
+
631
+ vim .lsp .util .apply_text_edits ({ { range = range , newText = newText } }, bufnr , encoding )
632
+
581
633
-- Position cursor at the end of the last inserted line
582
- vim .api .nvim_win_set_cursor (0 , { range [" start" ].line + # lines , # last_line })
634
+ local new_cursor_line = range [" start" ].line + # lines
635
+ vim .api .nvim_win_set_cursor (0 , { new_cursor_line , last_col })
636
+
637
+ if accepted_partial then
638
+ suggestion .partial_text = nil
639
+
640
+ for _ = 1 , lines_count - 1 do
641
+ suggestion .text = suggestion .text :sub (suggestion .text :find (" \n " ) + 1 )
642
+ suggestion .displayText = suggestion .displayText :sub (suggestion .displayText :find (" \n " ) + 1 )
643
+ end
644
+
645
+ update_ctx_suggestion_position (ctx .choice , new_cursor_line - 1 , last_col , bufnr )
646
+ update_preview (ctx )
647
+ end
583
648
end )()
584
649
end
585
650
@@ -592,29 +657,30 @@ function M.accept_word()
592
657
593
658
local _ , char_idx = string.find (text , " %s*%p*[^%s%p]*%s*" , character + 1 )
594
659
if char_idx then
595
- suggestion .text = string.sub (text , 1 , char_idx )
596
-
597
- range [" end" ].line = range [" start" ].line
660
+ suggestion .partial_text = string.sub (text , 1 , char_idx )
598
661
range [" end" ].character = char_idx
599
662
end
600
663
664
+ range [" end" ].line = range [" start" ].line
601
665
return suggestion
602
666
end )
603
667
end
604
668
605
669
function M .accept_line ()
606
670
M .accept (function (suggestion )
607
- local text = suggestion .text
671
+ local range , text = suggestion . range , suggestion .text
608
672
609
673
local cursor = vim .api .nvim_win_get_cursor (0 )
610
674
local _ , character = cursor [1 ], cursor [2 ]
611
675
612
676
local next_char = string.sub (text , character + 1 , character + 1 )
613
677
local _ , char_idx = string.find (text , next_char == " \n " and " \n %s*[^\n ]*\n %s*" or " \n %s*" , character )
614
678
if char_idx then
615
- suggestion .text = string.sub (text , 1 , char_idx )
679
+ suggestion .partial_text = string.sub (text , 1 , char_idx )
680
+ range [" end" ].character = char_idx
616
681
end
617
682
683
+ range [" end" ].line = range [" start" ].line
618
684
return suggestion
619
685
end )
620
686
end
@@ -659,6 +725,11 @@ local function on_buf_enter()
659
725
end
660
726
661
727
local function on_cursor_moved_i ()
728
+ if ignore_next_cursor_moved then
729
+ ignore_next_cursor_moved = false
730
+ return
731
+ end
732
+
662
733
local ctx = get_ctx ()
663
734
if copilot ._copilot_timer or ctx .params or should_auto_trigger () then
664
735
logger .trace (" suggestion on cursor moved insert" )
0 commit comments