Skip to content

Commit 09e5979

Browse files
committed
feat: copilot panel completions
1 parent 15fa6d1 commit 09e5979

File tree

2 files changed

+215
-3
lines changed

2 files changed

+215
-3
lines changed

clients/lsp-copilot.el

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,210 @@ The input are the file name and the major mode of the buffer."
9191
:path "copilot-language-server"
9292
:version lsp-copilot-version))
9393

94+
95+
;;; Panel Completion
96+
97+
(defvar-local lsp-copilot-panel-completion-items nil
98+
"A list of completion items returned by the Panel Completion call")
99+
100+
(defvar-local lsp-copilot-panel-completion-token nil
101+
"The per-request token")
102+
103+
(defvar-local lsp-copilot-panel-modification-tick nil
104+
"Modification tick when the panel was called")
105+
106+
(defun lsp-copilot--panel-accept-item (completing-buffer-name original-tick item)
107+
(when (buffer-live-p (get-buffer completing-buffer-name))
108+
(with-current-buffer completing-buffer-name
109+
(unless (= original-tick (buffer-chars-modified-tick))
110+
(user-error "Can not accept the suggestion on a modified buffer. Try copying it"))
111+
112+
(-let* (((&copilot-ls:PanelCompletionItem? :insert-text :range? :command?) item)
113+
((start . end) (when range?
114+
(-let (((&RangeToPoint :start :end) range?)) (cons start end)))))
115+
116+
(unless (and start end)
117+
(error "Server did not provide a range -- Can not insert"))
118+
119+
(lsp-with-undo-amalgamate
120+
(delete-region start end)
121+
(goto-char start)
122+
(insert insert-text))
123+
124+
;; Post command
125+
(when command?
126+
(lsp--execute-command command?))))))
127+
128+
(defun lsp-copilot--panel-accept-suggestion-at-point ()
129+
(interactive)
130+
(let ((completing-buffer-name (get-text-property (point) 'lsp-panel-completing-buffer-name))
131+
(original-tick (get-text-property (point) 'lsp-original-tick))
132+
(item (get-text-property (point) 'lsp-panel-item)))
133+
(lsp-copilot--panel-accept-item completing-buffer-name original-tick item)
134+
(quit-window)))
135+
136+
(defun lsp-copilot--panel-accept-button-click (b)
137+
(when-let* ((item (overlay-get b 'lsp-panel-item))
138+
(completing-buffer-name (overlay-get b 'lsp-panel-completing-buffer-name))
139+
(original-tick (overlay-get b 'lsp-original-tick)))
140+
(lsp-copilot--panel-accept-item completing-buffer-name original-tick item)
141+
142+
(quit-window)))
143+
144+
(defun lsp-copilot--panel-copy-button-click (b)
145+
146+
(kill-new (lsp:copilot-ls-panel-completion-item-insert-text (overlay-get b 'lsp-panel-item)))
147+
(quit-window))
148+
149+
(defun lsp-copilot--panel-forward-suggestion (arg)
150+
(interactive "p")
151+
(org-forward-heading-same-level arg)
152+
(recenter-top-bottom 0)
153+
(org-down-element))
154+
155+
(defun lsp-copilot--panel-backward-suggestion (arg)
156+
(interactive "p")
157+
(org-backward-heading-same-level arg)
158+
(recenter-top-bottom 0)
159+
(org-down-element))
160+
161+
(define-derived-mode lsp-copilot-panel-buffer-mode org-mode "CopilotPanel"
162+
"A major mode for completions "
163+
:group 'lsp-copilot)
164+
165+
166+
(let ((key-bindings '(("C-n" . lsp-copilot--panel-forward-suggestion)
167+
("C-p" . lsp-copilot--panel-backward-suggestion)
168+
("C-<return>" . lsp-copilot--panel-accept-suggestion-at-point)
169+
("q" . quit-window))))
170+
(dolist (binding key-bindings)
171+
(bind-key (kbd (car binding)) (cdr binding) 'lsp-copilot-panel-buffer-mode-map)))
172+
173+
174+
(defcustom lsp-copilot-panel-display-fn #'pop-to-buffer
175+
"Function used to display the panel completions buffer"
176+
:type 'function
177+
:group 'lsp-copilot)
178+
179+
180+
(defun lsp-copilot--panel-display-buffer (completing-buffer-name items original-tick)
181+
"Builds and display the panel buffer"
182+
(if (lsp-inline-completion--active-p)
183+
(progn
184+
(lsp-inline-completion-cancel)))
185+
186+
(when (and (bound-and-true-p company-mode)
187+
(company--active-p))
188+
(company-cancel))
189+
190+
(let ((buf (get-buffer-create (format "*lsp-copilot-panel-results-%s*" completing-buffer-name) )))
191+
(with-current-buffer buf
192+
(read-only-mode -1)
193+
(erase-buffer)
194+
(fundamental-mode)
195+
196+
;; (insert "#+STARTUP: noindent\n\n")
197+
(setq-local org-startup-indented nil)
198+
(cl-loop for item in items
199+
for i from 1
200+
do
201+
(-let* ((start-point (point))
202+
((&copilot-ls:PanelCompletionItem? :insert-text) item)
203+
(heading (format "* Solution %d" i))
204+
(src (format "#+begin_src %s\n%s\n#+end_src\n" "python" insert-text)))
205+
206+
(insert heading)
207+
(insert ?\n ?\n)
208+
(insert-button "Accept"
209+
'action #'lsp-copilot--panel-accept-button-click
210+
'lsp-original-tick original-tick
211+
'lsp-panel-item item
212+
'lsp-panel-completing-buffer-name completing-buffer-name)
213+
214+
(insert ?\s)
215+
216+
(insert-button "Copy"
217+
'action #'lsp-copilot--panel-copy-button-click
218+
'lsp-original-tick original-tick
219+
'lsp-panel-item item
220+
'lsp-panel-completing-buffer-name completing-buffer-name)
221+
222+
(insert ?\n)
223+
(insert src)
224+
(insert ?\n ?\n)
225+
226+
(put-text-property start-point (point) 'lsp-original-tick original-tick)
227+
(put-text-property start-point (point) 'lsp-panel-item item)
228+
(put-text-property start-point (point) 'lsp-panel-completing-buffer-name completing-buffer-name)))
229+
230+
(delete-all-space t)
231+
(lsp-copilot-panel-buffer-mode)
232+
(read-only-mode +1)
233+
234+
(goto-char (point-min))
235+
(org-down-element))
236+
237+
(if (get-buffer-window completing-buffer-name 'visible)
238+
(progn
239+
(select-window (get-buffer-window completing-buffer-name 'visible))
240+
(funcall lsp-copilot-panel-display-fn buf))
241+
(user-error "The original buffer for completions was not active; not showing panel"))))
242+
243+
244+
(defun lsp-copilot-panel-display-buffer ()
245+
"Displays a completion panel with the items collected by the last call of lsp-copilot-panel-completion"
246+
(interactive)
247+
248+
(if lsp-copilot-panel-completion-items
249+
(lsp-copilot--panel-display-buffer (buffer-name) lsp-copilot-panel-completion-items lsp-copilot-panel-modification-tick)
250+
(lsp--error "No completions to display")))
251+
252+
(defun lsp-copilot--panel-completions-progress-handler (workspace params)
253+
(-let* (((&ProgressParams :token :value) params)
254+
((action completing-buffer-name panel-completion-token) (string-split token " /// " )))
255+
(pcase action
256+
;; copilot sends results in the report
257+
("PANEL-PARTIAL-RESULT"
258+
(when (and (lsp-copilot-ls-panel-completion-items? value)
259+
(buffer-live-p (get-buffer completing-buffer-name)))
260+
(with-current-buffer completing-buffer-name
261+
(when (string-equal panel-completion-token lsp-copilot-panel-completion-token)
262+
(setq-local lsp-copilot-panel-completion-items
263+
(nconc lsp-copilot-panel-completion-items
264+
(seq-into (lsp:copilot-ls-panel-completion-items-items value) 'list)))))))
265+
("PANEL-WORK-DONE"
266+
(when (and (lsp-work-done-progress-end? value)
267+
(buffer-live-p (get-buffer completing-buffer-name))
268+
(string-equal (lsp:work-done-progress-end-kind value) "end"))
269+
(with-current-buffer completing-buffer-name
270+
(when (string-equal panel-completion-token lsp-copilot-panel-completion-token)
271+
(lsp-copilot-panel-display-buffer))))))))
272+
273+
(defun lsp-copilot-panel-completion ()
274+
"Use a Completion Panel to provide suggestions at point"
275+
(interactive)
276+
277+
(lsp-inline-completion-inhibit-idle-completions)
278+
(lsp-inline-completion-cancel-timer)
279+
280+
(let* ((token (uuidgen-1))
281+
(partial-token (format "PANEL-PARTIAL-RESULT /// %s /// %s" (buffer-name) token))
282+
(done-token (format "PANEL-WORK-DONE /// %s /// %s" (buffer-name) token))
283+
(params (lsp-make-copilot-ls-panel-completion-params :text-document (lsp--text-document-identifier)
284+
:position (lsp--cur-position)
285+
:partial-result-token partial-token
286+
:work-done-token done-token)))
287+
(setq-local lsp-copilot-panel-modification-tick (buffer-chars-modified-tick))
288+
(setq-local lsp-copilot-panel-completion-token token)
289+
(setq-local lsp-copilot-panel-completion-items nil)
290+
291+
;; call the complation and do not wait for any result -- the completions
292+
;; will be provided via $/progress notifications
293+
(lsp-request-async "textDocument/copilotPanelCompletion" params #'ignore)))
294+
295+
296+
;;; LSP Client
297+
94298
(defun lsp-copilot--find-active-workspaces ()
95299
"Returns a list of lsp-copilot workspaces"
96300
(-some->> (lsp-session)
@@ -190,13 +394,18 @@ automatically, browse to %s." user-code verification-uri))
190394
:name "emacs"
191395
:version "0.1.0"))
192396

397+
(defun lsp-copilot--progress-callback (workspace params)
398+
(when lsp-progress-function
399+
(funcall lsp-progress-function workspace params))
400+
401+
(lsp-copilot--panel-completions-progress-handler workspace params))
402+
193403
(defun lsp-copilot--server-initialized-fn (workspace)
194404
;; Patch capabilities -- server may respond with an empty dict. In plist,
195405
;; this would become nil
196406
(let ((caps (lsp--workspace-server-capabilities workspace)))
197407
(lsp:set-server-capabilities-inline-completion-provider? caps t))
198408

199-
200409
(when lsp-copilot-auth-check-delay
201410
(run-at-time lsp-copilot-auth-check-delay
202411
nil
@@ -224,7 +433,7 @@ automatically, browse to %s." user-code verification-uri))
224433
:download-server-fn (lambda (_client callback error-callback _update?)
225434
(lsp-package-ensure 'copilot-ls callback error-callback))
226435
:notification-handlers (lsp-ht
227-
("$/progress" (lambda (&rest args) (lsp-message "$/progress with %S" args)))
436+
("$/progress" #'lsp-copilot--progress-callback)
228437
("featureFlagsNotification" #'ignore)
229438
("statusNotification" #'ignore)
230439
("didChangeStatus" #'ignore)

lsp-protocol.el

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,10 @@ See `-let' for a description of the destructuring mechanism."
473473
(lsp-interface
474474
(copilot-ls:SignInInitiateResponse (:status :userCode :verificationUri :expiresIn :interval :user) nil)
475475
(copilot-ls:SignInConfirmResponse (:status :user))
476-
(copilot-ls:CheckStatusResponse (:status :user)))
476+
(copilot-ls:CheckStatusResponse (:status :user))
477+
(copilot-ls:PanelCompletionParams (:textDocument :position :partialResultToken :workDoneToken))
478+
(copilot-ls:PanelCompletionItem (:insertText) (:range :command))
479+
(copilot-ls:PanelCompletionItems (:items) nil))
477480

478481

479482
;; begin autogenerated code

0 commit comments

Comments
 (0)