@@ -9,7 +9,7 @@ local utils = require("copilot.client.utils")
99
1010local M = {}
1111
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 }
1313
1414local copilot = {
1515 setup_done = false ,
@@ -26,6 +26,8 @@ local copilot = {
2626 debounce = 75 ,
2727}
2828
29+ local ignore_next_cursor_moved = false
30+
2931local function with_client (fn )
3032 local client = c .get ()
3133 if client then
@@ -58,6 +60,28 @@ local function get_ctx(bufnr)
5860 return ctx
5961end
6062
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+
6185--- @param idx integer
6286--- @param text string
6387--- @param bufnr ? integer
@@ -89,6 +113,7 @@ local function reset_ctx(ctx)
89113 ctx .suggestions = nil
90114 ctx .choice = nil
91115 ctx .shown_choices = nil
116+ ctx .accepted_partial = nil
92117end
93118
94119local function set_keymap (keymap )
@@ -249,10 +274,10 @@ local function get_current_suggestion(ctx)
249274 return nil
250275 end
251276
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
256281
257282 return choice
258283 end )
@@ -475,7 +500,7 @@ local function schedule(ctx)
475500 stop_timer ()
476501 end
477502
478- update_preview (ctx )
503+ -- update_preview(ctx)
479504 local bufnr = vim .api .nvim_get_current_buf ()
480505 copilot ._copilot_timer = vim .fn .timer_start (copilot .debounce , function (timer )
481506 logger .trace (" suggestion schedule timer" , bufnr )
@@ -487,6 +512,10 @@ function M.next()
487512 local ctx = get_ctx ()
488513 logger .trace (" suggestion next" , ctx )
489514
515+ if ctx .accepted_partial then
516+ reset_ctx (ctx )
517+ end
518+
490519 -- no suggestion request yet
491520 if not ctx .first then
492521 logger .trace (" suggestion next, no first request" )
@@ -503,6 +532,10 @@ function M.prev()
503532 local ctx = get_ctx ()
504533 logger .trace (" suggestion prev" , ctx )
505534
535+ if ctx .accepted_partial then
536+ reset_ctx (ctx )
537+ end
538+
506539 -- no suggestion request yet
507540 if not ctx .first then
508541 logger .trace (" suggestion prev, no first request" , ctx )
@@ -532,13 +565,17 @@ function M.accept(modifier)
532565 return
533566 end
534567
535- cancel_inflight_requests (ctx )
536- reset_ctx (ctx )
537-
538568 if type (modifier ) == " function" then
539569 suggestion = modifier (suggestion )
540570 end
541571
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+
542579 with_client (function (client )
543580 local ok , _ = pcall (function ()
544581 api .notify_accepted (
@@ -552,34 +589,62 @@ function M.accept(modifier)
552589 end
553590 end )
554591
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
556602
557- local range , newText = suggestion .range , suggestion . text
603+ local range = suggestion .range
558604 local cursor = vim .api .nvim_win_get_cursor (0 )
559605 local line , character = cursor [1 ] - 1 , cursor [2 ]
560606 if range [" end" ].line == line and range [" end" ].character < character then
561607 range [" end" ].character = character
562608 end
563609
564- -- Hack for 'autoindent', makes the indent persist. Check `:help 'autoindent'`.
565610 vim .schedule_wrap (function ()
566611 -- Create an undo breakpoint
567612 vim .cmd (" let &undolevels=&undolevels" )
613+ -- Hack for 'autoindent', makes the indent persist. Check `:help 'autoindent'`.
568614 vim .api .nvim_feedkeys (vim .api .nvim_replace_termcodes (" <Space><Left><Del>" , true , false , true ), " n" , false )
569615 local bufnr = vim .api .nvim_get_current_buf ()
616+
570617 local encoding = vim .api .nvim_get_option_value (" fileencoding" , { buf = bufnr }) ~= " "
571618 and vim .api .nvim_get_option_value (" fileencoding" , { buf = bufnr })
572619 or vim .api .nvim_get_option_value (" encoding" , { scope = " global" })
573- vim .lsp .util .apply_text_edits ({ { range = range , newText = newText } }, bufnr , encoding )
574620
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)
578621 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+
581633 -- 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
583648 end )()
584649end
585650
@@ -592,29 +657,30 @@ function M.accept_word()
592657
593658 local _ , char_idx = string.find (text , " %s*%p*[^%s%p]*%s*" , character + 1 )
594659 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 )
598661 range [" end" ].character = char_idx
599662 end
600663
664+ range [" end" ].line = range [" start" ].line
601665 return suggestion
602666 end )
603667end
604668
605669function M .accept_line ()
606670 M .accept (function (suggestion )
607- local text = suggestion .text
671+ local range , text = suggestion . range , suggestion .text
608672
609673 local cursor = vim .api .nvim_win_get_cursor (0 )
610674 local _ , character = cursor [1 ], cursor [2 ]
611675
612676 local next_char = string.sub (text , character + 1 , character + 1 )
613677 local _ , char_idx = string.find (text , next_char == " \n " and " \n %s*[^\n ]*\n %s*" or " \n %s*" , character )
614678 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
616681 end
617682
683+ range [" end" ].line = range [" start" ].line
618684 return suggestion
619685 end )
620686end
@@ -659,6 +725,11 @@ local function on_buf_enter()
659725end
660726
661727local function on_cursor_moved_i ()
728+ if ignore_next_cursor_moved then
729+ ignore_next_cursor_moved = false
730+ return
731+ end
732+
662733 local ctx = get_ctx ()
663734 if copilot ._copilot_timer or ctx .params or should_auto_trigger () then
664735 logger .trace (" suggestion on cursor moved insert" )
0 commit comments