dotfiles/emacs/.emacs.d/elpa/lsp-mode-20260716.755/lsp-inline-completion.el

537 lines
22 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; lsp-inline-completion.el --- LSP mode -*- lexical-binding: t; -*-
;; Copyright (C) 2020-2026 emacs-lsp maintainers
;; Author: Rodrigo Kassick
;; Keywords: languages
;; Package-Requires: ((emacs "28.1") (dash "2.18.0") (spinner "1.7.3"))
;; URL: https://github.com/emacs-lsp/lsp-mode
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Inline Completions support
;; Specification here https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_inlineCompletion
;;; Code:
(require 'lsp-mode)
(defun lsp-inline-completion--params (implicit &optional identifier position)
"Returns a InlineCompletionParams instance"
(lsp-make-inline-completion-params
:textDocument (or identifier (lsp--text-document-identifier))
:position (or position (lsp--cur-position))
:context (lsp-make-inline-completion-context
:triggerKind (if implicit
lsp/inline-completion-trigger-automatic
lsp/inline-completion-trigger-invoked))))
(defun lsp-inline-completion--parse-items (response)
"Parses the response from the server and returns a list of
InlineCompletionItem objects"
(pcase response
;; Server responded with a completion list
((lsp-interface InlineCompletionList :items)
(seq-into items 'list))
;; Server responded with a sequence of completion items
((pred (lambda (i)
(and (sequencep i)
(lsp-inline-completion-item? (elt i 0)))))
(seq-into response 'list))
;; A sequence means multiple server may have responded. Iterate over them and normalize
((pred sequencep)
(let ((item-seq (cl-map 'list #'lsp-inline-completion--parse-items response)))
(apply 'seq-concatenate `(list ,@item-seq))))))
;;;;;; Default UI -- overlay
(defun lsp-inline-completion--define-key (keymap key def &optional keep)
"Defines key on map. With non-nil keep, will mark it as a command that
should not cancel the inline completion UI"
(define-key keymap key def)
(when keep
(pcase def
;; Command is OK
((pred symbolp)
(put def 'lsp-inline-completion-keep t))
;; ("str" . command) is OK
(`(,_str . ,defn)
(if (symbolp defn)
(put defn 'lsp-inline-completion-keep t)
(error "Unexpected type (str . defn) key definition %S" (type-of defn))))
;; Keymaps and macros are unsupported
(t (error "Unsupported definition with keep -- %S" def)))))
(defvar lsp-inline-completion-active-map
(let ((map (make-sparse-keymap)))
;; accept
(lsp-inline-completion--define-key map (kbd "C-<return>") #'lsp-inline-completion-accept 'keep)
(lsp-inline-completion--define-key map [mouse-1] #'lsp-inline-completion-accept-on-click 'keep)
;; navigate
(lsp-inline-completion--define-key map (kbd "C-n") #'lsp-inline-completion-next 'keep)
(lsp-inline-completion--define-key map (kbd "C-p") #'lsp-inline-completion-prev 'keep)
;; cancel
(lsp-inline-completion--define-key map (kbd "C-g") #'lsp-inline-completion-cancel)
(lsp-inline-completion--define-key map (kbd "<escape>") #'lsp-inline-completion-cancel)
(lsp-inline-completion--define-key map (kbd "C-c C-k") #'lsp-inline-completion-cancel)
;; useful -- recenter without losing the completion
(lsp-inline-completion--define-key map (kbd "C-l") #'recenter-top-bottom 'keep)
;; ignore
(lsp-inline-completion--define-key map [down-mouse-1] #'ignore 'keep)
(lsp-inline-completion--define-key map [up-mouse-1] #'ignore 'keep)
(lsp-inline-completion--define-key map [mouse-movement] #'ignore 'keep)
map)
"Keymap active when showing inline code suggestions.
When adding new bindings to this map, prefer using
lsp-inline-completion--define-key. Use a non-nil keep unless the command
should cancel the completion UI")
(defsubst lsp-inline-completion--keep (cmd)
"Returns t if the command should not cancel the current completion"
(and (symbolp cmd) (get cmd 'lsp-inline-completion-keep)))
(defface lsp-inline-completion-overlay-face
'((t :inherit shadow))
"Face for the inline code suggestions overlay."
:group 'lsp-mode)
;; Local Buffer State
(defvar-local lsp-inline-completion--items nil "The completions provided by the server.")
(defvar-local lsp-inline-completion--current nil "The current suggestion to be displayed.")
(defvar-local lsp-inline-completion--overlay nil "The overlay displaying code suggestions.")
(defvar-local lsp-inline-completion--start-point nil "The point where the completion started.")
(defcustom lsp-before-inline-completion-hook nil
"Hooks run before starting code suggestions."
:type 'hook
:group 'lsp-mode)
(defcustom lsp-after-inline-completion-hook nil
"Hooks executed after asking for code suggestions."
:type 'hook
:group 'lsp-mode)
(defcustom lsp-inline-completion-accepted-functions nil
"Functions executed after accepting a code suggestion.
The functions receive the inserted text and the range that was updated by the completion."
:type 'hook
:group 'lsp-mode)
(defcustom lsp-inline-completion-cancelled-hook nil
"Hooks executed after cancelling the completion UI."
:type 'hook
:group 'lsp-mode)
(defcustom lsp-inline-completion-before-show-hook nil
"Hooks executed before showing a suggestion."
:type 'hook
:group 'lsp-mode)
(defcustom lsp-inline-completion-shown-hook nil
"Hooks executed after showing a suggestion."
:type 'hook
:group 'lsp-mode)
(defcustom lsp-inline-completion-overlay-priority 9000
"The priority of the overlay."
:type '(choice (const :tag "No Priority" nil)
(integer :tag "Simple, Overriding Priority")
(cons :tag "Composite"
(choice (integer :tag "Primary")
(const :tag "Primary Unset" nil))
(integer :tag "Secondary")))
:group 'lsp-mode)
(defsubst lsp-inline-completion--active-p ()
"Returns whether we are in an active completion"
(overlayp lsp-inline-completion--overlay))
(defun lsp-inline-completion--clear-overlay ()
"Hide the suggestion overlay."
(when (overlayp lsp-inline-completion--overlay)
(delete-overlay lsp-inline-completion--overlay))
(setq lsp-inline-completion--overlay nil)
(internal-pop-keymap lsp-inline-completion-active-map 'overriding-terminal-local-map)
(remove-hook 'pre-command-hook #'lsp-inline-completion--pre-command-hook t))
(defun lsp-inline-completion--show-keys ()
"Shows active keymap hints in the minibuffer."
(unless (and lsp-inline-completion--items
(numberp lsp-inline-completion--current))
(error "No completions to show"))
(let ((message-log-max nil))
(message (concat "Completion "
(propertize (format "%d" (1+ lsp-inline-completion--current)) 'face 'bold)
"/"
(propertize (format "%d" (length lsp-inline-completion--items)) 'face 'bold)
(-when-let (keys (where-is-internal #'lsp-inline-completion-next lsp-inline-completion-active-map))
(concat ". "
(propertize " Next" 'face 'italic)
(format ": [%s]"
(string-join (--map (propertize (key-description it) 'face 'help-key-binding)
keys)
"/"))))
(-when-let (keys (where-is-internal #'lsp-inline-completion-accept lsp-inline-completion-active-map))
(concat (propertize " Accept" 'face 'italic)
(format ": [%s]"
(string-join (--map (propertize (key-description it) 'face 'help-key-binding)
keys)
"/"))))))))
(defun lsp-inline-completion--get-overlay (beg end)
"Build the suggestions overlay."
(lsp-inline-completion--clear-overlay)
(add-hook 'pre-command-hook #'lsp-inline-completion--pre-command-hook nil t)
(setq lsp-inline-completion--overlay (make-overlay beg end nil nil t))
(overlay-put lsp-inline-completion--overlay 'priority lsp-inline-completion-overlay-priority)
(internal-push-keymap lsp-inline-completion-active-map 'overriding-terminal-local-map)
(lsp-inline-completion--show-keys)
lsp-inline-completion--overlay)
(defun lsp-inline-completion-show-overlay ()
"Makes the suggestion overlay visible."
(unless (and lsp-inline-completion--items
(numberp lsp-inline-completion--current))
(error "No completions to show"))
(lsp-inline-completion--clear-overlay)
(run-hooks 'lsp-inline-completion-before-show-hook)
(-let* ((suggestion
(elt lsp-inline-completion--items
lsp-inline-completion--current))
((&InlineCompletionItem? :insert-text :range? (&RangeToPoint :start :end)) suggestion)
(start-point (or start (point)))
(showing-at-eol (save-excursion
(goto-char start-point)
(and (not (bolp)) (eolp))))
(beg (if showing-at-eol (1- start-point) start-point))
(end-point (or end (1+ beg)))
(text (cond
((lsp-markup-content? insert-text) (lsp:markup-content-value insert-text))
(t insert-text)))
(propertized-text (concat
(buffer-substring beg start-point)
(propertize text 'face 'lsp-inline-completion-overlay-face)))
(ov (lsp-inline-completion--get-overlay beg end-point))
display-str after-str target-position)
(goto-char beg)
(put-text-property 0 (length propertized-text) 'cursor t propertized-text)
(if (string-prefix-p
(buffer-substring-no-properties beg lsp-inline-completion--start-point)
text)
(progn
;; Show the prefix as `display'
(setq display-str (substring propertized-text 0 (- lsp-inline-completion--start-point beg)))
(setq after-str (substring propertized-text (- lsp-inline-completion--start-point beg) nil))
(setq target-position lsp-inline-completion--start-point))
(setq display-str (substring propertized-text 0 1))
(setq after-str (substring propertized-text 1))
(setq target-position beg))
(overlay-put ov 'display display-str)
(overlay-put ov 'after-string after-str)
(goto-char target-position)
(run-hooks 'lsp-inline-completion-shown-hook)))
(defun lsp-inline-completion--insert-sugestion (text kind start end command?)
(let* ((text-insert-start (or start lsp-inline-completion--start-point))
text-insert-end)
(when text-insert-start
(goto-char text-insert-start))
;; When range is provided, must replace the text of the range by the text
;; to insert
(when (and start end (/= start end))
(delete-region start end))
;; Insert suggestion, keeping the cursor at the start point
(insert text)
(setq text-insert-end (point))
;; If a template, format it -- keep track of the end position!
(when (eq kind 'snippet)
(let ((end-marker (set-marker (make-marker) (point))))
(lsp--expand-snippet (buffer-substring text-insert-start text-insert-end)
text-insert-start
text-insert-end)
(setq text-insert-end (marker-position end-marker))
(set-marker end-marker nil)))
;; Post command
(when command?
(lsp--execute-command command?))
;; hooks
(run-hook-with-args 'lsp-inline-completion-accepted-functions text text-insert-start text-insert-end)))
(defun lsp-inline-completion-accept ()
"Accepts the current suggestion."
(interactive)
(unless (lsp-inline-completion--active-p)
(error "Not showing suggestions"))
(lsp-inline-completion--clear-overlay)
(-let* ((suggestion (elt lsp-inline-completion--items lsp-inline-completion--current))
((&InlineCompletionItem? :insert-text :range? :command?) suggestion)
((kind . text) (cond
((lsp-markup-content? insert-text)
(cons 'snippet (lsp:markup-content-value insert-text) ))
(t (cons 'text insert-text))))
((start . end) (when range?
(-let (((&RangeToPoint :start :end) range?)) (cons start end)))))
(with-no-warnings
;; Compiler does not believes this macro is defined
(lsp-with-undo-amalgamate
(lsp-inline-completion--insert-sugestion text kind start end command?)))))
(defun lsp-inline-completion-accept-on-click (event)
(interactive "e")
(lsp-inline-completion-accept)
(-let (((col . row) (posn-actual-col-row (event-end event))))
(move-to-window-line row)
(beginning-of-line)
(forward-char (- col
(if (bound-and-true-p display-line-numbers-mode)
(+ 2 (line-number-display-width))
0)))))
(defun lsp-inline-completion-cancel ()
"Close the suggestion overlay."
(interactive)
(message nil) ;; clear echo
(let ((was-active (lsp-inline-completion--active-p)))
(lsp-inline-completion--clear-overlay)
(when was-active
(goto-char lsp-inline-completion--start-point)
(run-hooks 'lsp-inline-completion-cancelled-hook))))
(defun lsp-inline-completion-next ()
"Display the next inline completion."
(interactive)
(unless (lsp-inline-completion--active-p)
(error "Not showing suggestions"))
(setq lsp-inline-completion--current
(mod (1+ lsp-inline-completion--current)
(length lsp-inline-completion--items)))
(lsp-inline-completion-show-overlay))
(defun lsp-inline-completion-prev ()
"Display the previous inline completion."
(interactive)
(unless (lsp-inline-completion--active-p)
(error "Not showing suggestions"))
(setq lsp-inline-completion--current
(mod (1- lsp-inline-completion--current)
(length lsp-inline-completion--items)))
(lsp-inline-completion-show-overlay))
;;;###autoload
(defun lsp-inline-completion-display (&optional implicit)
"Displays the inline completions overlay."
(interactive)
(unless implicit
(lsp--spinner-start))
(run-hooks 'lsp-before-inline-completion-hook)
(condition-case err
(unwind-protect
(if-let* ((resp (lsp-request-while-no-input "textDocument/inlineCompletion"
(lsp-inline-completion--params implicit)))
(items (lsp-inline-completion--parse-items resp)))
(progn
(lsp-inline-completion--clear-overlay)
(setq lsp-inline-completion--items items)
(setq lsp-inline-completion--current 0)
(setq lsp-inline-completion--start-point (point))
(lsp-inline-completion-show-overlay))
(unless implicit
(lsp--info "No Suggestions!")))
;; Clean up
(unless implicit
(lsp--spinner-stop)))
(t (lsp--error "Could not fetch completions: %s" err))))
(defun lsp-inline-completion--pre-command-hook ()
"Cancels the inline completio before a non-keep command"
(when (and (lsp-inline-completion--active-p)
(not (lsp-inline-completion--keep this-command)))
(lsp-inline-completion-cancel)))
;; Inline Completion Mode
;;;###autoload
(defcustom lsp-inline-completion-enable t
"If non-nil it will enable inline completions on idle."
:type 'boolean
:group 'lsp-mode
:package-version '(lsp-mode . "10.0.0"))
(defcustom lsp-inline-completion-idle-delay 2
"The number of seconds before trying to fetch inline completions, when
lsp-inline-completion-mode is active."
:type 'number
:group 'lsp-mode
:package-version '(lsp-mode . "10.0.0"))
(defcustom lsp-inline-completion-inhibit-predicates nil
"When a function of this list returns non nil, lsp-inline-completion-mode will not show the completion."
:type '(repeat function)
:group 'lsp-mode)
(defvar-local lsp-inline-completion--idle-timer nil
"The idle timer used by lsp-inline-completion-mode.")
;;;###autoload
(define-minor-mode lsp-inline-completion-mode
"Mode automatically displaying inline completions."
:lighter nil
(cond
((and lsp-inline-completion-mode lsp--buffer-workspaces)
(add-hook 'lsp-on-change-hook #'lsp-inline-completion--after-change nil t))
(t
(when lsp-inline-completion--idle-timer
(cancel-timer lsp-inline-completion--idle-timer))
(lsp-inline-completion-cancel)
(remove-hook 'lsp-on-change-hook #'lsp-inline-completion--after-change t))))
(defun lsp-inline-completion--maybe-display (original-buffer original-point)
;; This is executed on an idle timer -- ensure state did not change before
;; displaying
(when (and (buffer-live-p original-buffer)
(eq (current-buffer) original-buffer)
(eq (point) original-point)
(--none? (funcall it) lsp-inline-completion-inhibit-predicates))
(setq last-command this-command)
(setq this-command 'lsp-inline-completion-display)
(lsp-inline-completion-display 'implicit)))
(defun lsp-inline-completion--after-change (&rest _)
;; This function is in lsp-on-change-hooks, which is executed on a timer by
;; lsp-on-change. Do not assume that the buffer/window state has not been
;; modified in the meantime! Use the values in lsp--after-change-vals to
;; ensure this.
(when lsp-inline-completion--idle-timer
(cancel-timer lsp-inline-completion--idle-timer))
(when (and lsp-inline-completion-mode lsp--buffer-workspaces)
(let ((original-buffer (plist-get lsp--after-change-vals :buffer))
(original-point (plist-get lsp--after-change-vals :point)))
(setq lsp-inline-completion--idle-timer
(run-with-idle-timer lsp-inline-completion-idle-delay
nil
#'lsp-inline-completion--maybe-display
original-buffer
original-point)))))
;;;###autoload
(add-hook 'lsp-configure-hook (lambda ()
(when (and lsp-inline-completion-enable
(lsp-feature? "textDocument/inlineCompletion"))
(lsp-inline-completion-mode))))
;; Company default integration
(declare-function company--active-p "ext:company")
(declare-function company-cancel "ext:company" (&optional result))
(declare-function company-manual-begin "ext:company")
(defvar company--begin-inhibit-commands)
(defcustom lsp-inline-completion-mode-inhibit-when-company-active t
"If the inline completion mode should avoid calling completions when company is active."
:type 'boolean
:group 'lsp-mode)
(defvar-local lsp-inline-completion--showing-company nil "If company was active when the tooltip is shown.")
(defun lsp-inline-completion--company-save-state-and-hide ()
(setq lsp-inline-completion--showing-company
(and (bound-and-true-p company-mode)
(company--active-p)))
(when lsp-inline-completion--showing-company
(company-cancel)))
(defun lsp-inline-completion--company-restore-state ()
(when lsp-inline-completion--showing-company
(company-manual-begin))
(setq lsp-inline-completion--showing-company nil))
(defun lsp-inline-completion--company-active-p ()
(and (bound-and-true-p company-mode) (company--active-p)))
;;;###autoload
(define-minor-mode lsp-inline-completion-company-integration-mode
"Minor mode to be used when company mode is active with lsp-inline-completion-mode."
:lighter nil
(cond
((and lsp-inline-completion-company-integration-mode lsp--buffer-workspaces (bound-and-true-p company-mode))
(add-hook 'lsp-inline-completion-before-show-hook #'lsp-inline-completion--company-save-state-and-hide nil t)
(add-hook 'lsp-inline-completion-cancelled-hook #'lsp-inline-completion--company-restore-state nil t)
(unless (memq #'lsp-inline-completion-display company--begin-inhibit-commands)
(setq-local company--begin-inhibit-commands
(cons #'lsp-inline-completion-display company--begin-inhibit-commands)))
(when (and lsp-inline-completion-mode-inhibit-when-company-active
(not (memq #'lsp-inline-completion--company-active-p lsp-inline-completion-inhibit-predicates)))
(setq-local lsp-inline-completion-inhibit-predicates
(cons #'lsp-inline-completion--company-active-p lsp-inline-completion-inhibit-predicates))))
(t
(remove-hook 'lsp-inline-completion-before-show-hook #'lsp-inline-completion--company-save-state-and-hide t)
(remove-hook 'lsp-inline-completion-cancelled-hook #'lsp-inline-completion--company-save-state-and-hide t)
(when (boundp 'company--begin-inhibit-commands)
(setq-local company--begin-inhibit-commands (delq #'lsp-inline-completion-display company--begin-inhibit-commands)))
(setq-local lsp-inline-completion-inhibit-predicates
(delq #'lsp-inline-completion--company-active-p lsp-inline-completion-inhibit-predicates)))))
(provide 'lsp-inline-completion)
;;; lsp-inline-completion.el ends here