lsp-inline-completion.el (21216B)
1 ;;; lsp-inline-completion.el --- LSP mode -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020-2024 emacs-lsp maintainers 4 5 ;; Author: Rodrigo Kassick 6 ;; Keywords: languages 7 ;; Package-Requires: ((emacs "27.1") (dash "2.18.0") (spinner "1.7.3")) 8 9 ;; URL: https://github.com/emacs-lsp/lsp-mode 10 ;; This program is free software; you can redistribute it and/or modify 11 ;; it under the terms of the GNU General Public License as published by 12 ;; the Free Software Foundation, either version 3 of the License, or 13 ;; (at your option) any later version. 14 15 ;; This program is distributed in the hope that it will be useful, 16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 ;; GNU General Public License for more details. 19 20 ;; You should have received a copy of the GNU General Public License 21 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 22 23 ;;; Commentary: 24 25 ;; Inline Completions support 26 ;; Specification here https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_inlineCompletion 27 28 ;;; Code: 29 30 (require 'lsp-mode) 31 32 (defun lsp-inline-completion--params (implicit &optional identifier position) 33 "Returns a InlineCompletionParams instance" 34 (lsp-make-inline-completion-params 35 :textDocument (or identifier (lsp--text-document-identifier)) 36 :position (or position (lsp--cur-position)) 37 :context (lsp-make-inline-completion-context 38 :triggerKind (if implicit 39 lsp/inline-completion-trigger-automatic 40 lsp/inline-completion-trigger-invoked)))) 41 42 (defun lsp-inline-completion--parse-items (response) 43 "Parses the reponse from the server and returns a list of 44 InlineCompletionItem objects" 45 46 (pcase response 47 ;; Server responded with a completion list 48 ((lsp-interface InlineCompletionList :items) 49 (seq-into items 'list)) 50 51 ;; Server responded with a sequence of completion items 52 ((pred (lambda (i) 53 (and (sequencep i) 54 (lsp-inline-completion-item? (elt i 0))))) 55 (seq-into response 'list)) 56 57 ;; A sequence means multiple server may have responded. Iterate over them and normalize 58 ((pred sequencep) 59 (let ((item-seq (cl-map 'list #'lsp-inline-completion--parse-items response))) 60 (apply 'seq-concatenate `(list ,@item-seq)))))) 61 62 ;;;;;; Default UI -- overlay 63 64 (defvar lsp-inline-completion-active-map 65 (let ((map (make-sparse-keymap))) 66 ;; accept 67 (define-key map (kbd "C-<return>") #'lsp-inline-completion-accept) 68 (define-key map [mouse-1] #'lsp-inline-completion-accept-on-click) 69 ;; navigate 70 (define-key map (kbd "C-n") #'lsp-inline-completion-next) 71 (define-key map (kbd "C-p") #'lsp-inline-completion-prev) 72 ;; cancel 73 (define-key map (kbd "C-g") #'lsp-inline-completion-cancel) 74 (define-key map (kbd "<escape>") #'lsp-inline-completion-cancel) 75 (define-key map (kbd "C-c C-k") #'lsp-inline-completion-cancel) 76 ;; useful -- recenter without loosing the completion 77 (define-key map (kbd "C-l") #'recenter-top-bottom) 78 ;; ignore 79 (define-key map [down-mouse-1] #'ignore) 80 (define-key map [up-mouse-1] #'ignore) 81 (define-key map [mouse-movement] #'ignore) 82 ;; Any event outside of the map, cancel and use it 83 (define-key map [t] #'lsp-inline-completion-cancel-with-input) 84 map) 85 "Keymap active when showing inline code suggestions") 86 87 (defface lsp-inline-completion-overlay-face 88 '((t :inherit shadow)) 89 "Face for the inline code suggestions overlay." 90 :group 'lsp-mode) 91 92 ;; Local Buffer State 93 94 (defvar-local lsp-inline-completion--items nil "The completions provided by the server") 95 (defvar-local lsp-inline-completion--current nil "The current suggestion to be displayed") 96 (defvar-local lsp-inline-completion--overlay nil "The overlay displaying code suggestions") 97 (defvar-local lsp-inline-completion--start-point nil "The point where the completion started") 98 99 (defcustom lsp-before-inline-completion-hook nil 100 "Hooks run before starting code suggestions" 101 :type 'hook 102 :group 'lsp-mode) 103 104 (defcustom lsp-after-inline-completion-hook nil 105 "Hooks executed after asking for code suggestions." 106 :type 'hook 107 :group 'lsp-mode) 108 109 (defcustom lsp-inline-completion-accepted-hook nil 110 "Hooks executed after accepting a code suggestion. The hooks receive the 111 text range that was updated by the completion" 112 :type 'hook 113 :group 'lsp-mode) 114 115 (defcustom lsp-inline-completion-cancelled-hook nil 116 "Hooks executed after cancelling the completion UI" 117 :type 'hook 118 :group 'lsp-mode) 119 120 (defcustom lsp-inline-completion-before-show-hook nil 121 "Hooks executed before showing a suggestion." 122 :type 'hook 123 :group 'lsp-mode) 124 125 (defcustom lsp-inline-completion-shown-hook nil 126 "Hooks executed after showing a suggestion." 127 :type 'hook 128 :group 'lsp-mode) 129 130 (defcustom lsp-inline-completion-overlay-priority 9000 131 "The priority of the overlay." 132 :type '(choice (const :tag "No Priority" nil) 133 (integer :tag "Simple, Overriding Priority") 134 (cons :tag "Composite" 135 (choice (integer :tag "Primary") 136 (const :tag "Primary Unset" nil)) 137 (integer :tag "Secondary"))) 138 :group 'lsp-mode) 139 140 (defsubst lsp-inline-completion--overlay-visible () 141 "Return whether the `overlay' is avaiable." 142 (and (overlayp lsp-inline-completion--overlay) 143 (overlay-buffer lsp-inline-completion--overlay))) 144 145 (defun lsp-inline-completion--clear-overlay () 146 "Hide the suggestion overlay" 147 (when (lsp-inline-completion--overlay-visible) 148 (delete-overlay lsp-inline-completion--overlay)) 149 (setq lsp-inline-completion--overlay nil)) 150 151 152 (defun lsp-inline-completion--get-overlay (beg end) 153 "Build the suggestions overlay" 154 (when (overlayp lsp-inline-completion--overlay) 155 (lsp-inline-completion--clear-overlay)) 156 157 (setq lsp-inline-completion--overlay (make-overlay beg end nil nil t)) 158 (overlay-put lsp-inline-completion--overlay 'keymap lsp-inline-completion-active-map) 159 (overlay-put lsp-inline-completion--overlay 'priority lsp-inline-completion-overlay-priority) 160 161 lsp-inline-completion--overlay) 162 163 164 (defun lsp-inline-completion--show-keys () 165 "Shows active keymap hints in the minibuffer" 166 167 (unless (and lsp-inline-completion--items 168 (numberp lsp-inline-completion--current)) 169 (error "No completions to show")) 170 171 (let ((message-log-max nil)) 172 (message (concat "Completion " 173 (propertize (format "%d" (1+ lsp-inline-completion--current)) 'face 'bold) 174 "/" 175 (propertize (format "%d" (length lsp-inline-completion--items)) 'face 'bold) 176 177 (-when-let (keys (where-is-internal #'lsp-inline-completion-next lsp-inline-completion-active-map)) 178 (concat ". " 179 (propertize " Next" 'face 'italic) 180 (format ": [%s]" 181 (string-join (--map (propertize (key-description it) 'face 'help-key-binding) 182 keys) 183 "/")))) 184 (-when-let (keys (where-is-internal #'lsp-inline-completion-accept lsp-inline-completion-active-map)) 185 (concat (propertize " Accept" 'face 'italic) 186 (format ": [%s]" 187 (string-join (--map (propertize (key-description it) 'face 'help-key-binding) 188 keys) 189 "/")))))))) 190 191 (defun lsp-inline-completion-show-overlay () 192 "Makes the suggestion overlay visible" 193 (unless (and lsp-inline-completion--items 194 (numberp lsp-inline-completion--current)) 195 (error "No completions to show")) 196 197 (lsp-inline-completion--clear-overlay) 198 199 (run-hooks 'lsp-inline-completion-before-show-hook) 200 201 (-let* ((suggestion 202 (elt lsp-inline-completion--items 203 lsp-inline-completion--current)) 204 ((&InlineCompletionItem? :insert-text :range?) suggestion) 205 ((&RangeToPoint :start :end) range?) 206 (start-point (or start (point))) 207 (showing-at-eol (save-excursion 208 (goto-char start-point) 209 (eolp))) 210 (beg (if showing-at-eol (1- start-point) start-point)) 211 (end-point (or end (1+ beg))) 212 (text (cond 213 ((lsp-markup-content? insert-text) (lsp:markup-content-value insert-text)) 214 (t insert-text))) 215 (propertizedText (concat 216 (buffer-substring beg start-point) 217 (propertize text 'face 'lsp-inline-completion-overlay-face))) 218 (ov (lsp-inline-completion--get-overlay beg end-point)) 219 (completion-is-substr (string-equal 220 (buffer-substring beg lsp-inline-completion--start-point) 221 (substring propertizedText 0 (- lsp-inline-completion--start-point beg)))) 222 display-str after-str target-position) 223 224 (goto-char beg) 225 226 (put-text-property 0 (length propertizedText) 'cursor t propertizedText) 227 228 (if completion-is-substr 229 (progn 230 ;; Show the prefix as `display' 231 (setq display-str (substring propertizedText 0 (- lsp-inline-completion--start-point beg))) 232 (setq after-str (substring propertizedText (- lsp-inline-completion--start-point beg) nil)) 233 (setq target-position lsp-inline-completion--start-point)) 234 235 236 (setq display-str (substring propertizedText 0 1)) 237 (setq after-str (substring propertizedText 1)) 238 (setq target-position beg)) 239 240 (overlay-put ov 'display display-str) 241 (overlay-put ov 'after-string after-str) 242 243 (goto-char target-position) 244 245 (lsp-inline-completion--show-keys) 246 (run-hooks 'lsp-inline-completion-shown-hook))) 247 248 (defun lsp-inline-completion--insert-sugestion (text kind start end command?) 249 (let* ((text-insert-start (or start lsp-inline-completion--start-point)) 250 text-insert-end 251 (completion-is-substr (string-equal 252 (buffer-substring text-insert-start lsp-inline-completion--start-point) 253 (substring text 0 (- lsp-inline-completion--start-point text-insert-start))))) 254 (when text-insert-start 255 (goto-char text-insert-start)) 256 257 ;; When range is provided, must replace the text of the range by the text 258 ;; to insert 259 (when (and start end (/= start end)) 260 (delete-region start end)) 261 262 ;; Insert suggestion, keeping the cursor at the start point 263 (insert text) 264 265 (setq text-insert-end (point)) 266 267 ;; If a template, format it -- keep track of the end position! 268 (when (eq kind 'snippet) 269 (let ((end-marker (set-marker (make-marker) (point)))) 270 (lsp--expand-snippet (buffer-substring text-insert-start text-insert-end) 271 text-insert-start 272 text-insert-end) 273 (setq text-insert-end (marker-position end-marker)) 274 (set-marker end-marker nil))) 275 276 ;; Post command 277 (when command? 278 (lsp--execute-command command?)) 279 280 (if completion-is-substr 281 (goto-char lsp-inline-completion--start-point) 282 (goto-char text-insert-start)) 283 284 ;; hooks 285 (run-hook-with-args-until-failure 'lsp-inline-completion-accepted-hook text text-insert-start text-insert-end))) 286 287 (defun lsp-inline-completion-accept () 288 "Accepts the current suggestion" 289 (interactive) 290 (unless (lsp-inline-completion--overlay-visible) 291 (error "Not showing suggestions")) 292 293 (lsp-inline-completion--clear-overlay) 294 (-let* ((suggestion (elt lsp-inline-completion--items lsp-inline-completion--current)) 295 ((&InlineCompletionItem? :insert-text :range? :command?) suggestion) 296 ((kind . text) (cond 297 ((lsp-markup-content? insert-text) 298 (cons 'snippet (lsp:markup-content-value insert-text) )) 299 (t (cons 'text insert-text)))) 300 ((start . end) (when range? 301 (-let (((&RangeToPoint :start :end) range?)) (cons start end))))) 302 303 (with-no-warnings 304 ;; Compiler does not believes this macro is defined 305 (lsp-with-undo-amalgamate 306 (lsp-inline-completion--insert-sugestion text kind start end command?))))) 307 308 (defun lsp-inline-completion-accept-on-click (event) 309 (interactive "e") 310 311 (lsp-inline-completion-accept) 312 (-let (((col . row) (posn-actual-col-row (event-end event)))) 313 (move-to-window-line row) 314 (beginning-of-line) 315 (forward-char (- col 316 (if (bound-and-true-p display-line-numbers-mode) 317 (+ 2 (line-number-display-width)) 318 0))))) 319 320 (defun lsp-inline-completion-cancel () 321 "Close the suggestion overlay" 322 (interactive) 323 (when (lsp-inline-completion--overlay-visible) 324 325 (lsp-inline-completion--clear-overlay) 326 327 (when lsp-inline-completion--start-point 328 (goto-char lsp-inline-completion--start-point)) 329 330 (run-hooks 'lsp-inline-completion-cancelled-hook))) 331 332 (defun lsp-inline-completion-cancel-with-input (event &optional arg) 333 "Cancel the inline completion and executes whatever event was received" 334 (interactive (list last-input-event current-prefix-arg)) 335 336 (lsp-inline-completion-cancel) 337 338 (let ((command (lookup-key (current-active-maps) (vector event))) 339 (current-prefix-arg arg)) 340 341 (when (commandp command) 342 (call-interactively command)))) 343 344 (defun lsp-inline-completion-next () 345 "Display the next inline completion" 346 (interactive) 347 (unless (lsp-inline-completion--overlay-visible) 348 (error "Not showing suggestions")) 349 (setq lsp-inline-completion--current 350 (mod (1+ lsp-inline-completion--current) 351 (length lsp-inline-completion--items))) 352 353 (lsp-inline-completion-show-overlay)) 354 355 (defun lsp-inline-completion-prev () 356 "Display the previous inline completion" 357 (interactive) 358 (unless (lsp-inline-completion--overlay-visible) 359 (error "Not showing suggestions")) 360 (setq lsp-inline-completion--current 361 (mod (1- lsp-inline-completion--current) 362 (length lsp-inline-completion--items))) 363 364 (lsp-inline-completion-show-overlay)) 365 366 ;;;###autoload 367 (defun lsp-inline-completion-display (&optional implicit) 368 "Displays the inline completions overlay" 369 (interactive) 370 371 (unless implicit 372 (lsp--spinner-start) ) 373 374 (unwind-protect 375 (if-let* ((resp (lsp-request-while-no-input "textDocument/inlineCompletion" 376 (lsp-inline-completion--params implicit))) 377 (items (lsp-inline-completion--parse-items resp))) 378 379 (progn 380 (lsp-inline-completion--clear-overlay) 381 (setq lsp-inline-completion--items items) 382 (setq lsp-inline-completion--current 0) 383 (setq lsp-inline-completion--start-point (point)) 384 (lsp-inline-completion-show-overlay)) 385 (unless implicit 386 (lsp--info "No Suggestions!"))) 387 ;; Clean up 388 (unless implicit 389 (lsp--spinner-stop)))) 390 391 392 ;; Inline Completion Mode 393 ;;;###autoload 394 (defcustom lsp-inline-completion-enable t 395 "If non-nil it will enable inline completions on idle." 396 :type 'boolean 397 :group 'lsp-mode 398 :package-version '(lsp-mode . "9.0.1")) 399 400 (defcustom lsp-inline-completion-idle-delay 2 401 "The number of seconds before trying to fetch inline completions, when 402 lsp-inline-completion-mode is active" 403 :type 'number 404 :group 'lsp-mode 405 :package-version '(lsp-mode . "9.0.1")) 406 407 (defcustom lsp-inline-completion-inhibit-predicates nil 408 "When a function of this list returns non nil, lsp-inline-completion-mode will not show the completion" 409 :type '(repeat function) 410 :group 'lsp-mode) 411 412 (defvar-local lsp-inline-completion--idle-timer nil 413 "The idle timer used by lsp-inline-completion-mode") 414 415 ;;;###autoload 416 (define-minor-mode lsp-inline-completion-mode 417 "Mode automatically displaying inline completions." 418 :lighter nil 419 (cond 420 ((and lsp-inline-completion-mode lsp--buffer-workspaces) 421 (add-hook 'lsp-on-change-hook #'lsp-inline-completion--after-change nil t)) 422 (t 423 (when lsp-inline-completion--idle-timer 424 (cancel-timer lsp-inline-completion--idle-timer)) 425 426 (lsp-inline-completion-cancel) 427 428 (remove-hook 'lsp-on-change-hook #'lsp-inline-completion--after-change t)))) 429 430 (defun lsp-inline-completion--maybe-display (original-buffer original-point) 431 ;; This is executed on an idle timer -- ensure state did not change before 432 ;; displaying 433 (when (and (buffer-live-p original-buffer) 434 (eq (current-buffer) original-buffer) 435 (eq (point) original-point) 436 (--none? (funcall it) lsp-inline-completion-inhibit-predicates)) 437 (setq last-command this-command) 438 (setq this-command 'lsp-inline-completion-display) 439 (lsp-inline-completion-display 'implicit))) 440 441 (defun lsp-inline-completion--after-change (&rest _) 442 ;; This function is in lsp-on-change-hooks, which is executed on a timer by 443 ;; lsp-on-change. Do not assume that the buffer/window state has not been 444 ;; modified in the meantime! Use the values in lsp--after-change-vals to 445 ;; ensure this. 446 447 (when lsp-inline-completion--idle-timer 448 (cancel-timer lsp-inline-completion--idle-timer)) 449 450 (when (and lsp-inline-completion-mode lsp--buffer-workspaces) 451 (let ((original-buffer (plist-get lsp--after-change-vals :buffer)) 452 (original-point (plist-get lsp--after-change-vals :point))) 453 (setq lsp-inline-completion--idle-timer 454 (run-with-idle-timer lsp-inline-completion-idle-delay 455 nil 456 #'lsp-inline-completion--maybe-display 457 original-buffer 458 original-point))))) 459 460 ;;;###autoload 461 (add-hook 'lsp-configure-hook (lambda () 462 (when (and lsp-inline-completion-enable 463 (lsp-feature? "textDocument/inlineCompletion")) 464 (lsp-inline-completion-mode)))) 465 466 ;; Company default integration 467 468 (declare-function company--active-p "ext:company") 469 (declare-function company-cancel "ext:company" (&optional result)) 470 (declare-function company-manual-begin "ext:company") 471 (defvar company--begin-inhibit-commands) 472 (defcustom lsp-inline-completion-mode-inhibit-when-company-active t 473 "If the inline completion mode should avoid calling completions when company is active" 474 :type 'boolean 475 :group 'lsp-mode) 476 477 (defvar-local lsp-inline-completion--showing-company nil "If company was active when the tooltip is shown") 478 479 (defun lsp-inline-completion--company-save-state-and-hide () 480 (setq lsp-inline-completion--showing-company 481 (and (bound-and-true-p company-mode) 482 (company--active-p))) 483 484 (when lsp-inline-completion--showing-company 485 (company-cancel))) 486 487 (defun lsp-inline-completion--company-restore-state () 488 (when lsp-inline-completion--showing-company 489 (company-manual-begin)) 490 (setq lsp-inline-completion--showing-company nil)) 491 492 (defun lsp-inline-completion--company-active-p () 493 (and (bound-and-true-p company-mode) (company--active-p))) 494 495 ;;;###autoload 496 (define-minor-mode lsp-inline-completion-company-integration-mode 497 "Minor mode to be used when company mode is active with lsp-inline-completion-mode" 498 :lighter nil 499 (cond 500 ((and lsp-inline-completion-company-integration-mode lsp--buffer-workspaces (bound-and-true-p company-mode)) 501 (add-hook 'lsp-inline-completion-before-show-hook #'lsp-inline-completion--company-save-state-and-hide nil t) 502 (add-hook 'lsp-inline-completion-cancelled-hook #'lsp-inline-completion--company-restore-state nil t) 503 (unless (memq #'lsp-inline-completion-display company--begin-inhibit-commands) 504 (setq-local company--begin-inhibit-commands 505 (cons #'lsp-inline-completion-display company--begin-inhibit-commands))) 506 (when (and lsp-inline-completion-mode-inhibit-when-company-active 507 (not (memq #'lsp-inline-completion--company-active-p lsp-inline-completion-inhibit-predicates))) 508 (setq-local lsp-inline-completion-inhibit-predicates 509 (cons #'lsp-inline-completion--company-active-p lsp-inline-completion-inhibit-predicates)))) 510 511 (t 512 (remove-hook 'lsp-inline-completion-before-show-hook #'lsp-inline-completion--company-save-state-and-hide t) 513 (remove-hook 'lsp-inline-completion-cancelled-hook #'lsp-inline-completion--company-save-state-and-hide t) 514 (when (boundp 'company--begin-inhibit-commands) 515 (setq-local company--begin-inhibit-commands (delq #'lsp-inline-completion-display company--begin-inhibit-commands))) 516 (setq-local lsp-inline-completion-inhibit-predicates 517 (delq #'lsp-inline-completion--company-active-p lsp-inline-completion-inhibit-predicates))))) 518 519 (provide 'lsp-inline-completion)