;;; lsp-completion.el --- LSP completion -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2020 emacs-lsp maintainers ;; ;; 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 . ;; ;;; Commentary: ;; ;; LSP completion ;; ;;; Code: (require 'lsp-mode) (defgroup lsp-completion nil "LSP support for completion." :prefix "lsp-completion-" :group 'lsp-mode :tag "LSP Completion") ;;;###autoload (define-obsolete-variable-alias 'lsp-prefer-capf 'lsp-completion-provider "lsp-mode 7.0.1") (defcustom lsp-completion-provider :capf "The completion backend provider." :type '(choice (const :tag "Use company-capf" :capf) (const :tag "None" :none)) :group 'lsp-completion :package-version '(lsp-mode . "7.0.1")) ;;;###autoload (define-obsolete-variable-alias 'lsp-enable-completion-at-point 'lsp-completion-enable "lsp-mode 7.0.1") (defcustom lsp-completion-enable t "Enable `completion-at-point' integration." :type 'boolean :group 'lsp-completion) (defcustom lsp-completion-enable-additional-text-edit t "Whether or not to apply additional text edit when performing completion. If set to non-nil, `lsp-mode' will apply additional text edits from the server. Otherwise, the additional text edits are ignored." :type 'boolean :group 'lsp-completion :package-version '(lsp-mode . "6.3.2")) (defcustom lsp-completion-show-kind t "Whether or not to show kind of completion candidates." :type 'boolean :group 'lsp-completion :package-version '(lsp-mode . "7.0.1")) (defcustom lsp-completion-show-detail t "Whether or not to show detail of completion candidates." :type 'boolean :group 'lsp-completion) (defcustom lsp-completion-show-label-description t "Whether or not to show description of completion candidates." :type 'boolean :group 'lsp-completion :package-version '(lsp-mode . "8.0.1")) (defcustom lsp-completion-no-cache nil "Whether or not caching the returned completions from server." :type 'boolean :group 'lsp-completion :package-version '(lsp-mode . "7.0.1")) (defcustom lsp-completion-filter-on-incomplete t "Whether or not filter incomplete results." :type 'boolean :group 'lsp-completion :package-version '(lsp-mode . "7.0.1")) (defcustom lsp-completion-sort-initial-results t "Whether or not filter initial results from server." :type 'boolean :group 'lsp-completion :package-version '(lsp-mode . "8.0.0")) (defcustom lsp-completion-use-last-result t "Temporarily use last server result when interrupted by keyboard. This will help minimize popup flickering issue in `company-mode'." :type 'boolean :group 'lsp-completion :package-version '(lsp-mode . "8.0.0")) (defconst lsp-completion--item-kind [nil "Text" "Method" "Function" "Constructor" "Field" "Variable" "Class" "Interface" "Module" "Property" "Unit" "Value" "Enum" "Keyword" "Snippet" "Color" "File" "Reference" "Folder" "EnumMember" "Constant" "Struct" "Event" "Operator" "TypeParameter"]) (defvar yas-indent-line) (defvar company-backends) (defvar company-abort-on-unique-match) (defvar lsp-completion--no-reordering nil "Dont do client-side reordering completion items when set.") (declare-function company-mode "ext:company") (declare-function yas-expand-snippet "ext:yasnippet") (defun lsp-doc-buffer (&optional string) "Return doc for STRING." (with-current-buffer (get-buffer-create "*lsp-documentation*") (erase-buffer) (fundamental-mode) (when string (save-excursion (insert string) (visual-line-mode))) (current-buffer))) (defun lsp-falsy? (val) "Non-nil if VAL is falsy." ;; https://developer.mozilla.org/en-US/docs/Glossary/Falsy (or (not val) (equal val "") (equal val 0))) (cl-defun lsp-completion--make-item (item &key markers prefix) "Make completion item from lsp ITEM and with MARKERS and PREFIX." (-let (((&CompletionItem :label :sort-text? :_emacsStartPoint start-point) item)) (propertize label 'lsp-completion-item item 'lsp-sort-text sort-text? 'lsp-completion-start-point start-point 'lsp-completion-markers markers 'lsp-completion-prefix prefix))) (defun lsp-completion--annotate (item) "Annotate ITEM detail." (-let (((&CompletionItem :detail? :kind? :label-details?) (plist-get (text-properties-at 0 item) 'lsp-completion-item))) (concat (when (and lsp-completion-show-detail detail?) (concat " " (s-replace "\r" "" detail?))) (when (and lsp-completion-show-label-description label-details?) (when-let ((description (and label-details? (lsp:label-details-description label-details?)))) (format " %s" description))) (when lsp-completion-show-kind (when-let ((kind-name (and kind? (aref lsp-completion--item-kind kind?)))) (format " (%s)" kind-name)))))) (defun lsp-completion--looking-back-trigger-characterp (trigger-characters) "Return character if text before point match any of the TRIGGER-CHARACTERS." (unless (= (point) (line-beginning-position)) (seq-some (lambda (trigger-char) (and (equal (buffer-substring-no-properties (- (point) (length trigger-char)) (point)) trigger-char) trigger-char)) trigger-characters))) (defvar lsp-completion--cache nil "Cached candidates for completion at point function. In the form of plist (prefix-pos items :lsp-items :prefix ...). When the completion is incomplete, `items' contains value of :incomplete.") (defvar lsp-completion--last-result nil "Last completion result.") (defun lsp-completion--clear-cache (&optional keep-last-result) "Clear completion caches. KEEP-LAST-RESULT if specified." (-some-> lsp-completion--cache (cddr) (plist-get :markers) (cl-second) (set-marker nil)) (setq lsp-completion--cache nil) (unless keep-last-result (setq lsp-completion--last-result nil))) (defcustom lsp-completion-default-behaviour :replace "Default behaviour of `InsertReplaceEdit'." :type '(choice (const :insert :tag "Default completion inserts") (const :replace :tag "Default completion replaces")) :group 'lsp-mode :package-version '(lsp-mode . "8.0.0")) (lsp-defun lsp-completion--guess-prefix ((item &as &CompletionItem :text-edit?)) "Guess ITEM's prefix start point according to following heuristics: - If `textEdit' exists, use insertion range start as prefix start point. - Else, find the point before current point is longest prefix match of `insertText' or `label'. And: - The character before prefix is not word constitute Return `nil' when fails to guess prefix." (cond ((lsp-insert-replace-edit? text-edit?) (lsp--position-to-point (lsp:range-start (lsp:insert-replace-edit-insert text-edit?)))) (text-edit? (lsp--position-to-point (lsp:range-start (lsp:text-edit-range text-edit?)))) (t (-let* (((&CompletionItem :label :insert-text?) item) (text (or (unless (lsp-falsy? insert-text?) insert-text?) label)) (point (point)) (start (max 1 (- point (length text)))) (char-before (char-before start)) start-point) (while (and (< start point) (not start-point)) (unless (or (and char-before (equal (char-syntax char-before) ?w)) (not (string-prefix-p (buffer-substring-no-properties start point) text))) (setq start-point start)) (cl-incf start) (setq char-before (char-before start))) start-point)))) (defun lsp-completion--to-internal (items) "Convert ITEMS into internal form." (--> items (-map (-lambda ((item &as &CompletionItem :label :filter-text? :_emacsStartPoint start-point :score?)) `( :label ,(or (unless (lsp-falsy? filter-text?) filter-text?) label) :item ,item :start-point ,start-point :score ,score?)) it))) (cl-defun lsp-completion--filter-candidates (items &key lsp-items markers prefix &allow-other-keys) "List all possible completions in cached ITEMS with their prefixes. We can pass LSP-ITEMS, which will be used when there's no cache. The MARKERS and PREFIX value will be attached to each candidate." (lsp--while-no-input (->> (if items (--> (let (queries fuz-queries) (-keep (-lambda ((cand &as &plist :label :start-point :score)) (let* ((query (or (plist-get queries start-point) (let ((s (buffer-substring-no-properties start-point (point)))) (setq queries (plist-put queries start-point s)) s))) (fuz-query (or (plist-get fuz-queries start-point) (let ((s (lsp-completion--regex-fuz query))) (setq fuz-queries (plist-put fuz-queries start-point s)) s))) (label-len (length label))) (when (string-match fuz-query label) (put-text-property 0 label-len 'match-data (match-data) label) (plist-put cand :sort-score (* (or (lsp-completion--fuz-score query label) 1e-05) (or score 0.001))) cand))) items)) (if lsp-completion--no-reordering it (sort it (lambda (o1 o2) (> (plist-get o1 :sort-score) (plist-get o2 :sort-score))))) ;; TODO: pass additional function to sort the candidates (-map (-rpartial #'plist-get :item) it)) lsp-items) (-map (lambda (item) (lsp-completion--make-item item :markers markers :prefix prefix)))))) (defconst lsp-completion--kind->symbol '((1 . text) (2 . method) (3 . function) (4 . constructor) (5 . field) (6 . variable) (7 . class) (8 . interface) (9 . module) (10 . property) (11 . unit) (12 . value) (13 . enum) (14 . keyword) (15 . snippet) (16 . color) (17 . file) (18 . reference) (19 . folder) (20 . enum-member) (21 . constant) (22 . struct) (23 . event) (24 . operator) (25 . type-parameter))) (defun lsp-completion--candidate-kind (item) "Return ITEM's kind." (alist-get (lsp:completion-item-kind? (get-text-property 0 'lsp-completion-item item)) lsp-completion--kind->symbol)) (defun lsp-completion--candidate-deprecated (item) "Return if ITEM is deprecated." (let ((completion-item (get-text-property 0 'lsp-completion-item item))) (or (lsp:completion-item-deprecated? completion-item) (seq-position (lsp:completion-item-tags? completion-item) lsp/completion-item-tag-deprecated)))) (defun lsp-completion--company-match (candidate) "Return highlight of typed prefix inside CANDIDATE." (let* ((prefix (downcase (buffer-substring-no-properties (plist-get (text-properties-at 0 candidate) 'lsp-completion-start-point) (point)))) ;; Workaround for bug #4192 ;; `lsp-completion-start-point' above might be from cached/previous completion and ;; pointing to a very distant point, which results in `prefix' being way too long. ;; So let's consider only the first line. (prefix (car (s-lines prefix))) (prefix-len (length prefix)) (prefix-pos 0) (label (downcase candidate)) (label-len (length label)) (label-pos 0) matches start) (while (and (not matches) (< prefix-pos prefix-len)) (while (and (< prefix-pos prefix-len) (< label-pos label-len)) (if (equal (aref prefix prefix-pos) (aref label label-pos)) (progn (unless start (setq start label-pos)) (cl-incf prefix-pos)) (when start (setq matches (nconc matches `((,start . ,label-pos)))) (setq start nil))) (cl-incf label-pos)) (when start (setq matches (nconc matches `((,start . ,label-pos))))) ;; Search again when the whole prefix is not matched (when (< prefix-pos prefix-len) (setq matches nil)) ;; Start search from next offset of prefix to find a match with label (unless matches (cl-incf prefix-pos) (setq label-pos 0))) matches)) (defun lsp-completion--get-documentation (item) "Get doc comment for completion ITEM." (unless (get-text-property 0 'lsp-completion-resolved item) (let ((resolved-item (-some->> item (get-text-property 0 'lsp-completion-item) (lsp-completion--resolve))) (len (length item))) (put-text-property 0 len 'lsp-completion-item resolved-item item) (put-text-property 0 len 'lsp-completion-resolved t item))) (-some->> item (get-text-property 0 'lsp-completion-item) (lsp:completion-item-documentation?) (lsp--render-element))) (defun lsp-completion--get-context (trigger-characters same-session?) "Get completion context with provided TRIGGER-CHARACTERS and SAME-SESSION?." (let* ((triggered-by-char non-essential) (trigger-char (when triggered-by-char (lsp-completion--looking-back-trigger-characterp trigger-characters))) (trigger-kind (cond (trigger-char lsp/completion-trigger-kind-trigger-character) ((and same-session? (equal (cl-second lsp-completion--cache) :incomplete)) lsp/completion-trigger-kind-trigger-for-incomplete-completions) (t lsp/completion-trigger-kind-invoked)))) (apply #'lsp-make-completion-context (nconc `(:trigger-kind ,trigger-kind) (when trigger-char `(:trigger-character? ,trigger-char)))))) (defun lsp-completion--sort-completions (completions) "Sort COMPLETIONS." (sort completions (-lambda ((&CompletionItem :sort-text? sort-text-left :label label-left) (&CompletionItem :sort-text? sort-text-right :label label-right)) (if (equal sort-text-left sort-text-right) (string-lessp label-left label-right) (string-lessp sort-text-left sort-text-right))))) ;;;###autoload (defun lsp-completion-at-point () "Get lsp completions." (when (or (--some (lsp--client-completion-in-comments? (lsp--workspace-client it)) (lsp-workspaces)) (not (nth 4 (syntax-ppss)))) (let* ((trigger-chars (-> (lsp--capability-for-method "textDocument/completion") (lsp:completion-options-trigger-characters?))) (bounds-start (or (cl-first (bounds-of-thing-at-point 'symbol)) (point))) result done? (candidates (lambda () (lsp--catch 'input (let ((lsp--throw-on-input lsp-completion-use-last-result) (same-session? (and lsp-completion--cache ;; Special case for empty prefix and empty result (or (cl-second lsp-completion--cache) (not (string-empty-p (plist-get (cddr lsp-completion--cache) :prefix)))) (equal (cl-first lsp-completion--cache) bounds-start) (s-prefix? (plist-get (cddr lsp-completion--cache) :prefix) (buffer-substring-no-properties bounds-start (point)))))) (cond ((or done? result) result) ((and (not lsp-completion-no-cache) same-session? (listp (cl-second lsp-completion--cache))) (setf result (apply #'lsp-completion--filter-candidates (cdr lsp-completion--cache)))) (t (-let* ((resp (lsp-request-while-no-input "textDocument/completion" (plist-put (lsp--text-document-position-params) :context (lsp-completion--get-context trigger-chars same-session?)))) (completed (and resp (not (and (lsp-completion-list? resp) (lsp:completion-list-is-incomplete resp))))) (items (lsp--while-no-input (--> (cond ((lsp-completion-list? resp) (lsp:completion-list-items resp)) (t resp)) (if (or completed (seq-some #'lsp:completion-item-sort-text? it)) (lsp-completion--sort-completions it) it) (-map (lambda (item) (lsp-put item :_emacsStartPoint (or (lsp-completion--guess-prefix item) bounds-start))) it)))) (markers (list bounds-start (copy-marker (point) t))) (prefix (buffer-substring-no-properties bounds-start (point))) (lsp-completion--no-reordering (not lsp-completion-sort-initial-results))) (lsp-completion--clear-cache same-session?) (setf done? completed lsp-completion--cache (list bounds-start (cond ((and done? (not (seq-empty-p items))) (lsp-completion--to-internal items)) ((not done?) :incomplete)) :lsp-items nil :markers markers :prefix prefix) result (lsp-completion--filter-candidates (cond (done? (cl-second lsp-completion--cache)) (lsp-completion-filter-on-incomplete (lsp-completion--to-internal items))) :lsp-items items :markers markers :prefix prefix)))))) (:interrupted lsp-completion--last-result) (`,res (setq lsp-completion--last-result res)))))) (list bounds-start (point) (lambda (probe pred action) (cond ((eq action 'metadata) '(metadata (category . lsp-capf) (display-sort-function . identity) (cycle-sort-function . identity))) ((eq (car-safe action) 'boundaries) nil) (t (complete-with-action action (funcall candidates) probe pred)))) :annotation-function #'lsp-completion--annotate :company-kind #'lsp-completion--candidate-kind :company-deprecated #'lsp-completion--candidate-deprecated :company-require-match 'never :company-prefix-length (save-excursion (and (lsp-completion--looking-back-trigger-characterp trigger-chars) t)) :company-match #'lsp-completion--company-match :company-doc-buffer (-compose #'lsp-doc-buffer #'lsp-completion--get-documentation) :exit-function (-rpartial #'lsp-completion--exit-fn candidates))))) (defun lsp-completion--find-workspace (server-id) (--first (eq (lsp--client-server-id (lsp--workspace-client it)) server-id) (lsp-workspaces))) (defun lsp-completion--exit-fn (candidate _status &optional candidates) "Exit function of `completion-at-point'. CANDIDATE is the selected completion item. Others: CANDIDATES" (unwind-protect (-let* ((candidate (if (plist-member (text-properties-at 0 candidate) 'lsp-completion-item) candidate (cl-find candidate (funcall candidates) :test #'equal))) ((&plist 'lsp-completion-item item 'lsp-completion-start-point start-point 'lsp-completion-markers markers 'lsp-completion-resolved resolved 'lsp-completion-prefix prefix) (text-properties-at 0 candidate)) ((&CompletionItem? :label :insert-text? :text-edit? :insert-text-format? :additional-text-edits? :insert-text-mode? :command?) ;; see #3498 typescript-language-server does not provide the ;; proper insertText without resolving. (if (and (lsp-completion--find-workspace 'ts-ls) (not resolved)) (lsp-completion--resolve item) item))) (cond (text-edit? (apply #'delete-region markers) (insert prefix) (pcase text-edit? ((TextEdit) (lsp--apply-text-edit text-edit?)) ((InsertReplaceEdit :insert :replace :new-text) (lsp--apply-text-edit (lsp-make-text-edit :new-text new-text :range (if (or (and current-prefix-arg (eq lsp-completion-default-behaviour :replace)) (and (not current-prefix-arg) (eq lsp-completion-default-behaviour :insert))) insert replace)))))) ((or (unless (lsp-falsy? insert-text?) insert-text?) label) (apply #'delete-region markers) (insert prefix) (delete-region start-point (point)) (insert (or (unless (lsp-falsy? insert-text?) insert-text?) label)))) (lsp--indent-lines start-point (point) insert-text-mode?) (when (equal insert-text-format? lsp/insert-text-format-snippet) (lsp--expand-snippet (buffer-substring start-point (point)) start-point (point))) (when lsp-completion-enable-additional-text-edit (if (or (get-text-property 0 'lsp-completion-resolved candidate) (not (seq-empty-p additional-text-edits?))) (lsp--apply-text-edits additional-text-edits? 'completion) (-let [(callback cleanup-fn) (lsp--create-apply-text-edits-handlers)] (lsp-completion--resolve-async item (-compose callback #'lsp:completion-item-additional-text-edits?) cleanup-fn)))) (if (or (get-text-property 0 'lsp-completion-resolved candidate) command?) (when command? (lsp--execute-command command?)) (lsp-completion--resolve-async item (-lambda ((&CompletionItem? :command?)) (when command? (lsp--execute-command command?))))) (when (and (or (equal lsp-signature-auto-activate t) (memq :after-completion lsp-signature-auto-activate) (and (memq :on-trigger-char lsp-signature-auto-activate) (-when-let ((&SignatureHelpOptions? :trigger-characters?) (lsp--capability :signatureHelpProvider)) (lsp-completion--looking-back-trigger-characterp trigger-characters?)))) (lsp-feature? "textDocument/signatureHelp")) (lsp-signature-activate)) (setq-local lsp-inhibit-lsp-hooks nil)) (lsp-completion--clear-cache))) (defun lsp-completion--regex-fuz (str) "Build a regex sequence from STR. Insert .* between each char." (apply #'concat (cl-mapcar #'concat (cons "" (cdr (seq-map (lambda (c) (format "[^%c]*" c)) str))) (seq-map (lambda (c) (format "\\(%s\\)" (regexp-quote (char-to-string c)))) str)))) (defun lsp-completion--fuz-score (query str) "Calculate fuzzy score for STR with query QUERY. The return is nil or in range of (0, inf)." (-when-let* ((md (cddr (or (get-text-property 0 'match-data str) (let ((re (lsp-completion--regex-fuz query))) (when (string-match re str) (match-data)))))) (start (pop md)) (len (length str)) ;; To understand how this works, consider these bad ascii(tm) ;; diagrams showing how the pattern "foo" flex-matches ;; "fabrobazo", "fbarbazoo" and "barfoobaz": ;; f abr o baz o ;; + --- + --- + ;; f barbaz oo ;; + ------ ++ ;; bar foo baz ;; --- +++ --- ;; "+" indicates parts where the pattern matched. A "hole" in ;; the middle of the string is indicated by "-". Note that there ;; are no "holes" near the edges of the string. The completion ;; score is a number bound by ]0..1]: the higher the better and ;; only a perfect match (pattern equals string) will have score ;; 1. The formula takes the form of a quotient. For the ;; numerator, we use the number of +, i.e. the length of the ;; pattern. For the denominator, it first computes ;; ;; hole_i_contrib = 1 + (Li-1)^1.05 for first hole ;; hole_i_contrib = 1 + (Li-1)^0.25 for hole i of length Li ;; ;; The final value for the denominator is then given by: ;; ;; (SUM_across_i(hole_i_contrib) + 1) ;; (score-numerator 0) (score-denominator 0) (last-b -1) (q-ind 0) (update-score (lambda (a b) "Update score variables given match range (A B)." (setq score-numerator (+ score-numerator (- b a))) (unless (= a len) ;; case mismatch will be pushed to near next rank (unless (equal (aref query q-ind) (aref str a)) (cl-incf a 0.9)) (setq score-denominator (+ score-denominator (if (= a last-b) 0 (+ 1 (* (if (< 0 (- a last-b 1)) 1 -1) (expt (abs (- a last-b 1)) ;; Give a higher score for match near start (if (eq last-b -1) 0.75 0.25)))))))) (setq last-b b)))) (while md (funcall update-score start (cl-first md)) ;; Due to the way completion regex is constructed, `(eq end (+ start 1))` (cl-incf q-ind) (pop md) (setq start (pop md))) (unless (zerop len) (/ score-numerator (1+ score-denominator) 1.0)))) (defun lsp-completion--fix-resolve-data (item) "Patch `CompletionItem' ITEM for rust-analyzer otherwise resolve will fail. See #2675" (let ((data (lsp:completion-item-data? item))) (when (lsp-member? data :import_for_trait_assoc_item) (unless (lsp-get data :import_for_trait_assoc_item) (lsp-put data :import_for_trait_assoc_item :json-false))))) (defun lsp-completion--resolve (item) "Resolve completion ITEM." (cl-assert item nil "Completion item must not be nil") (lsp-completion--fix-resolve-data item) (or (ignore-errors (when (lsp-feature? "completionItem/resolve") (lsp-request "completionItem/resolve" (lsp-delete (lsp-copy item) :_emacsStartPoint)))) item)) (defun lsp-completion--resolve-async (item callback &optional cleanup-fn) "Resolve completion ITEM asynchronously with CALLBACK. The CLEANUP-FN will be called to cleanup." (cl-assert item nil "Completion item must not be nil") (lsp-completion--fix-resolve-data item) (ignore-errors (if (lsp-feature? "completionItem/resolve") (lsp-request-async "completionItem/resolve" (lsp-delete (lsp-copy item) :_emacsStartPoint) (lambda (result) (funcall callback result) (when cleanup-fn (funcall cleanup-fn))) :error-handler (lambda (err) (when cleanup-fn (funcall cleanup-fn)) (error (lsp:json-error-message err))) :cancel-handler cleanup-fn :mode 'alive) (funcall callback item) (when cleanup-fn (funcall cleanup-fn))))) ;;;###autoload (defun lsp-completion--enable () "Enable LSP completion support." (when (and lsp-completion-enable (lsp-feature? "textDocument/completion")) (lsp-completion-mode 1))) (defun lsp-completion--disable () "Disable LSP completion support." (lsp-completion-mode -1)) (defun lsp-completion-passthrough-all-completions (_string table pred _point) "Like `completion-basic-all-completions' but have prefix ignored. TABLE PRED" (completion-basic-all-completions "" table pred 0)) ;;;###autoload (define-minor-mode lsp-completion-mode "Toggle LSP completion support." :group 'lsp-completion :global nil :lighter "" (let ((completion-started-fn (lambda (&rest _) (setq-local lsp-inhibit-lsp-hooks t))) (after-completion-fn (lambda (result) (when (stringp result) (lsp-completion--clear-cache)) (setq-local lsp-inhibit-lsp-hooks nil)))) (cond (lsp-completion-mode (make-local-variable 'completion-at-point-functions) ;; Ensure that `lsp-completion-at-point' the first CAPF to be tried, ;; unless user has put it elsewhere in the list by their own (add-to-list 'completion-at-point-functions #'lsp-completion-at-point) (make-local-variable 'completion-category-defaults) (setf (alist-get 'lsp-capf completion-category-defaults) '((styles . (lsp-passthrough)))) (make-local-variable 'completion-styles-alist) (setf (alist-get 'lsp-passthrough completion-styles-alist) '(completion-basic-try-completion lsp-completion-passthrough-all-completions "Passthrough completion.")) (cond ((equal lsp-completion-provider :none)) ((and (not (equal lsp-completion-provider :none)) (fboundp 'company-mode)) (setq-local company-abort-on-unique-match nil) (company-mode 1) (setq-local company-backends (cl-adjoin 'company-capf company-backends :test #'equal))) (t (lsp--warn "Unable to autoconfigure company-mode."))) (when (bound-and-true-p company-mode) (add-hook 'company-completion-started-hook completion-started-fn nil t) (add-hook 'company-after-completion-hook after-completion-fn nil t)) (add-hook 'lsp-unconfigure-hook #'lsp-completion--disable nil t)) (t (remove-hook 'completion-at-point-functions #'lsp-completion-at-point t) (setq-local completion-category-defaults (cl-remove 'lsp-capf completion-category-defaults :key #'cl-first)) (setq-local completion-styles-alist (cl-remove 'lsp-passthrough completion-styles-alist :key #'cl-first)) (remove-hook 'lsp-unconfigure-hook #'lsp-completion--disable t) (when (featurep 'company) (remove-hook 'company-completion-started-hook completion-started-fn t) (remove-hook 'company-after-completion-hook after-completion-fn t)))))) ;;;###autoload (add-hook 'lsp-configure-hook (lambda () (when (and lsp-auto-configure lsp-completion-enable) (lsp-completion--enable)))) (lsp-consistency-check lsp-completion) (provide 'lsp-completion) ;;; lsp-completion.el ends here