lsp-completion.el (36586B)
1 ;;; lsp-completion.el --- LSP completion -*- lexical-binding: t; -*- 2 ;; 3 ;; Copyright (C) 2020 emacs-lsp maintainers 4 ;; 5 ;; This program is free software; you can redistribute it and/or modify 6 ;; it under the terms of the GNU General Public License as published by 7 ;; the Free Software Foundation, either version 3 of the License, or 8 ;; (at your option) any later version. 9 10 ;; This program is distributed in the hope that it will be useful, 11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 ;; GNU General Public License for more details. 14 15 ;; You should have received a copy of the GNU General Public License 16 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 17 ;; 18 ;;; Commentary: 19 ;; 20 ;; LSP completion 21 ;; 22 ;;; Code: 23 24 (require 'lsp-mode) 25 26 (defgroup lsp-completion nil 27 "LSP support for completion." 28 :prefix "lsp-completion-" 29 :group 'lsp-mode 30 :tag "LSP Completion") 31 32 ;;;###autoload 33 (define-obsolete-variable-alias 'lsp-prefer-capf 34 'lsp-completion-provider "lsp-mode 7.0.1") 35 36 (defcustom lsp-completion-provider :capf 37 "The completion backend provider." 38 :type '(choice 39 (const :tag "Use company-capf" :capf) 40 (const :tag "None" :none)) 41 :group 'lsp-completion 42 :package-version '(lsp-mode . "7.0.1")) 43 44 ;;;###autoload 45 (define-obsolete-variable-alias 'lsp-enable-completion-at-point 46 'lsp-completion-enable "lsp-mode 7.0.1") 47 48 (defcustom lsp-completion-enable t 49 "Enable `completion-at-point' integration." 50 :type 'boolean 51 :group 'lsp-completion) 52 53 (defcustom lsp-completion-enable-additional-text-edit t 54 "Whether or not to apply additional text edit when performing completion. 55 56 If set to non-nil, `lsp-mode' will apply additional text edits 57 from the server. Otherwise, the additional text edits are 58 ignored." 59 :type 'boolean 60 :group 'lsp-completion 61 :package-version '(lsp-mode . "6.3.2")) 62 63 (defcustom lsp-completion-show-kind t 64 "Whether or not to show kind of completion candidates." 65 :type 'boolean 66 :group 'lsp-completion 67 :package-version '(lsp-mode . "7.0.1")) 68 69 (defcustom lsp-completion-show-detail t 70 "Whether or not to show detail of completion candidates." 71 :type 'boolean 72 :group 'lsp-completion) 73 74 (defcustom lsp-completion-show-label-description t 75 "Whether or not to show description of completion candidates." 76 :type 'boolean 77 :group 'lsp-completion 78 :package-version '(lsp-mode . "9.0.0")) 79 80 (defcustom lsp-completion-no-cache nil 81 "Whether or not caching the returned completions from server." 82 :type 'boolean 83 :group 'lsp-completion 84 :package-version '(lsp-mode . "7.0.1")) 85 86 (defcustom lsp-completion-filter-on-incomplete t 87 "Whether or not filter incomplete results." 88 :type 'boolean 89 :group 'lsp-completion 90 :package-version '(lsp-mode . "7.0.1")) 91 92 (defcustom lsp-completion-sort-initial-results t 93 "Whether or not filter initial results from server." 94 :type 'boolean 95 :group 'lsp-completion 96 :package-version '(lsp-mode . "8.0.0")) 97 98 (defcustom lsp-completion-use-last-result t 99 "Temporarily use last server result when interrupted by keyboard. 100 This will help minimize popup flickering issue in `company-mode'." 101 :type 'boolean 102 :group 'lsp-completion 103 :package-version '(lsp-mode . "8.0.0")) 104 105 (defconst lsp-completion--item-kind 106 [nil 107 "Text" 108 "Method" 109 "Function" 110 "Constructor" 111 "Field" 112 "Variable" 113 "Class" 114 "Interface" 115 "Module" 116 "Property" 117 "Unit" 118 "Value" 119 "Enum" 120 "Keyword" 121 "Snippet" 122 "Color" 123 "File" 124 "Reference" 125 "Folder" 126 "EnumMember" 127 "Constant" 128 "Struct" 129 "Event" 130 "Operator" 131 "TypeParameter"]) 132 133 (defvar yas-indent-line) 134 (defvar company-backends) 135 (defvar company-abort-on-unique-match) 136 137 (defvar lsp-completion--no-reordering nil 138 "Dont do client-side reordering completion items when set.") 139 140 (declare-function company-mode "ext:company") 141 (declare-function yas-expand-snippet "ext:yasnippet") 142 143 (defun lsp-doc-buffer (&optional string) 144 "Return doc for STRING." 145 (with-current-buffer (get-buffer-create "*lsp-documentation*") 146 (erase-buffer) 147 (fundamental-mode) 148 (when string 149 (save-excursion 150 (insert string) 151 (visual-line-mode))) 152 (current-buffer))) 153 154 (defun lsp-falsy? (val) 155 "Non-nil if VAL is falsy." 156 ;; https://developer.mozilla.org/en-US/docs/Glossary/Falsy 157 (or (not val) (equal val "") (equal val 0))) 158 159 (cl-defun lsp-completion--make-item (item &key markers prefix) 160 "Make completion item from lsp ITEM and with MARKERS and PREFIX." 161 (-let (((&CompletionItem :label 162 :sort-text? 163 :_emacsStartPoint start-point) 164 item)) 165 (propertize label 166 'lsp-completion-item item 167 'lsp-sort-text sort-text? 168 'lsp-completion-start-point start-point 169 'lsp-completion-markers markers 170 'lsp-completion-prefix prefix))) 171 172 (defun lsp-completion--annotate (item) 173 "Annotate ITEM detail." 174 (-let (((&CompletionItem :detail? :kind? :label-details?) (plist-get (text-properties-at 0 item) 175 'lsp-completion-item))) 176 (concat (when (and lsp-completion-show-detail detail?) 177 (concat " " (s-replace "\r" "" detail?))) 178 (when (and lsp-completion-show-label-description label-details?) 179 (when-let ((description (and label-details? (lsp:label-details-description label-details?)))) 180 (format " %s" description))) 181 (when lsp-completion-show-kind 182 (when-let ((kind-name (and kind? (aref lsp-completion--item-kind kind?)))) 183 (format " (%s)" kind-name)))))) 184 185 (defun lsp-completion--looking-back-trigger-characterp (trigger-characters) 186 "Return character if text before point match any of the TRIGGER-CHARACTERS." 187 (unless (= (point) (line-beginning-position)) 188 (seq-some 189 (lambda (trigger-char) 190 (and (equal (buffer-substring-no-properties (- (point) (length trigger-char)) (point)) 191 trigger-char) 192 trigger-char)) 193 trigger-characters))) 194 195 (defvar lsp-completion--cache nil 196 "Cached candidates for completion at point function. 197 In the form of plist (prefix-pos items :lsp-items :prefix ...). 198 When the completion is incomplete, `items' contains value of :incomplete.") 199 200 (defvar lsp-completion--last-result nil 201 "Last completion result.") 202 203 (defun lsp-completion--clear-cache (&optional keep-last-result) 204 "Clear completion caches. 205 KEEP-LAST-RESULT if specified." 206 (-some-> lsp-completion--cache 207 (cddr) 208 (plist-get :markers) 209 (cl-second) 210 (set-marker nil)) 211 (setq lsp-completion--cache nil) 212 (unless keep-last-result (setq lsp-completion--last-result nil))) 213 214 (defcustom lsp-completion-default-behaviour :replace 215 "Default behaviour of `InsertReplaceEdit'." 216 :type '(choice 217 (const :insert :tag "Default completion inserts") 218 (const :replace :tag "Default completion replaces")) 219 :group 'lsp-mode 220 :package-version '(lsp-mode . "8.0.0")) 221 222 (lsp-defun lsp-completion--guess-prefix ((item &as &CompletionItem :text-edit?)) 223 "Guess ITEM's prefix start point according to following heuristics: 224 - If `textEdit' exists, use insertion range start as prefix start point. 225 - Else, find the point before current point is longest prefix match of 226 `insertText' or `label'. And: 227 - The character before prefix is not word constitute 228 Return `nil' when fails to guess prefix." 229 (cond 230 ((lsp-insert-replace-edit? text-edit?) 231 (lsp--position-to-point (lsp:range-start (lsp:insert-replace-edit-insert text-edit?)))) 232 (text-edit? 233 (lsp--position-to-point (lsp:range-start (lsp:text-edit-range text-edit?)))) 234 (t 235 (-let* (((&CompletionItem :label :insert-text?) item) 236 (text (or (unless (lsp-falsy? insert-text?) insert-text?) label)) 237 (point (point)) 238 (start (max 1 (- point (length text)))) 239 (char-before (char-before start)) 240 start-point) 241 (while (and (< start point) (not start-point)) 242 (unless (or (and char-before (equal (char-syntax char-before) ?w)) 243 (not (string-prefix-p (buffer-substring-no-properties start point) 244 text))) 245 (setq start-point start)) 246 (cl-incf start) 247 (setq char-before (char-before start))) 248 start-point)))) 249 250 (defun lsp-completion--to-internal (items) 251 "Convert ITEMS into internal form." 252 (--> items 253 (-map (-lambda ((item &as &CompletionItem 254 :label 255 :filter-text? 256 :_emacsStartPoint start-point 257 :score?)) 258 `( :label ,(or (unless (lsp-falsy? filter-text?) filter-text?) label) 259 :item ,item 260 :start-point ,start-point 261 :score ,score?)) 262 it))) 263 264 (cl-defun lsp-completion--filter-candidates (items &key 265 lsp-items 266 markers 267 prefix 268 &allow-other-keys) 269 "List all possible completions in cached ITEMS with their prefixes. 270 We can pass LSP-ITEMS, which will be used when there's no cache. 271 The MARKERS and PREFIX value will be attached to each candidate." 272 (lsp--while-no-input 273 (->> 274 (if items 275 (--> (let (queries fuz-queries) 276 (-keep (-lambda ((cand &as &plist :label :start-point :score)) 277 (let* ((query (or (plist-get queries start-point) 278 (let ((s (buffer-substring-no-properties 279 start-point (point)))) 280 (setq queries (plist-put queries start-point s)) 281 s))) 282 (fuz-query (or (plist-get fuz-queries start-point) 283 (let ((s (lsp-completion--regex-fuz query))) 284 (setq fuz-queries 285 (plist-put fuz-queries start-point s)) 286 s))) 287 (label-len (length label))) 288 (when (string-match fuz-query label) 289 (put-text-property 0 label-len 'match-data (match-data) label) 290 (plist-put cand 291 :sort-score 292 (* (or (lsp-completion--fuz-score query label) 1e-05) 293 (or score 0.001))) 294 cand))) 295 items)) 296 (if lsp-completion--no-reordering 297 it 298 (sort it (lambda (o1 o2) 299 (> (plist-get o1 :sort-score) 300 (plist-get o2 :sort-score))))) 301 ;; TODO: pass additional function to sort the candidates 302 (-map (-rpartial #'plist-get :item) it)) 303 lsp-items) 304 (-map (lambda (item) (lsp-completion--make-item item 305 :markers markers 306 :prefix prefix)))))) 307 308 (defconst lsp-completion--kind->symbol 309 '((1 . text) 310 (2 . method) 311 (3 . function) 312 (4 . constructor) 313 (5 . field) 314 (6 . variable) 315 (7 . class) 316 (8 . interface) 317 (9 . module) 318 (10 . property) 319 (11 . unit) 320 (12 . value) 321 (13 . enum) 322 (14 . keyword) 323 (15 . snippet) 324 (16 . color) 325 (17 . file) 326 (18 . reference) 327 (19 . folder) 328 (20 . enum-member) 329 (21 . constant) 330 (22 . struct) 331 (23 . event) 332 (24 . operator) 333 (25 . type-parameter))) 334 335 (defun lsp-completion--candidate-kind (item) 336 "Return ITEM's kind." 337 (alist-get (lsp:completion-item-kind? (get-text-property 0 'lsp-completion-item item)) 338 lsp-completion--kind->symbol)) 339 340 (defun lsp-completion--candidate-deprecated (item) 341 "Return if ITEM is deprecated." 342 (let ((completion-item (get-text-property 0 'lsp-completion-item item))) 343 (or (lsp:completion-item-deprecated? completion-item) 344 (seq-position (lsp:completion-item-tags? completion-item) 345 lsp/completion-item-tag-deprecated)))) 346 347 (defun lsp-completion--company-match (candidate) 348 "Return highlight of typed prefix inside CANDIDATE." 349 (if-let ((md (cddr (plist-get (text-properties-at 0 candidate) 'match-data)))) 350 (let (matches start end) 351 (while (progn (setq start (pop md) end (pop md)) 352 (and start end)) 353 (setq matches (nconc matches `((,start . ,end))))) 354 matches) 355 (let* ((prefix (downcase 356 (buffer-substring-no-properties 357 ;; Put a safe guard to prevent staled cache from setting a wrong start point #4192 358 (max (line-beginning-position) 359 (plist-get (text-properties-at 0 candidate) 'lsp-completion-start-point)) 360 (point)))) 361 (prefix-len (length prefix)) 362 (prefix-pos 0) 363 (label (downcase candidate)) 364 (label-len (length label)) 365 (label-pos 0) 366 matches start) 367 (while (and (not matches) 368 (< prefix-pos prefix-len)) 369 (while (and (< prefix-pos prefix-len) 370 (< label-pos label-len)) 371 (if (equal (aref prefix prefix-pos) (aref label label-pos)) 372 (progn 373 (unless start (setq start label-pos)) 374 (cl-incf prefix-pos)) 375 (when start 376 (setq matches (nconc matches `((,start . ,label-pos)))) 377 (setq start nil))) 378 (cl-incf label-pos)) 379 (when start (setq matches (nconc matches `((,start . ,label-pos))))) 380 ;; Search again when the whole prefix is not matched 381 (when (< prefix-pos prefix-len) 382 (setq matches nil)) 383 ;; Start search from next offset of prefix to find a match with label 384 (unless matches 385 (cl-incf prefix-pos) 386 (setq label-pos 0))) 387 matches))) 388 389 (defun lsp-completion--get-documentation (item) 390 "Get doc comment for completion ITEM." 391 (unless (get-text-property 0 'lsp-completion-resolved item) 392 (let ((resolved-item 393 (-some->> item 394 (get-text-property 0 'lsp-completion-item) 395 (lsp-completion--resolve))) 396 (len (length item))) 397 (put-text-property 0 len 'lsp-completion-item resolved-item item) 398 (put-text-property 0 len 'lsp-completion-resolved t item))) 399 (-some->> item 400 (get-text-property 0 'lsp-completion-item) 401 (lsp:completion-item-documentation?) 402 (lsp--render-element))) 403 404 (defun lsp-completion--get-context (trigger-characters same-session?) 405 "Get completion context with provided TRIGGER-CHARACTERS and SAME-SESSION?." 406 (let* ((triggered-by-char non-essential) 407 (trigger-char (when triggered-by-char 408 (lsp-completion--looking-back-trigger-characterp 409 trigger-characters))) 410 (trigger-kind (cond 411 (trigger-char 412 lsp/completion-trigger-kind-trigger-character) 413 ((and same-session? 414 (equal (cl-second lsp-completion--cache) :incomplete)) 415 lsp/completion-trigger-kind-trigger-for-incomplete-completions) 416 (t lsp/completion-trigger-kind-invoked)))) 417 (apply #'lsp-make-completion-context 418 (nconc 419 `(:trigger-kind ,trigger-kind) 420 (when trigger-char 421 `(:trigger-character? ,trigger-char)))))) 422 423 (defun lsp-completion--sort-completions (completions) 424 "Sort COMPLETIONS." 425 (sort 426 completions 427 (-lambda ((&CompletionItem :sort-text? sort-text-left :label label-left) 428 (&CompletionItem :sort-text? sort-text-right :label label-right)) 429 (if (equal sort-text-left sort-text-right) 430 (string-lessp label-left label-right) 431 (string-lessp sort-text-left sort-text-right))))) 432 433 ;;;###autoload 434 (defun lsp-completion-at-point () 435 "Get lsp completions." 436 (when (or (--some (lsp--client-completion-in-comments? (lsp--workspace-client it)) 437 (lsp-workspaces)) 438 (not (nth 4 (syntax-ppss)))) 439 (let* ((trigger-chars (-> (lsp--capability-for-method "textDocument/completion") 440 (lsp:completion-options-trigger-characters?))) 441 (bounds-start (or (cl-first (bounds-of-thing-at-point 'symbol)) 442 (point))) 443 result done? 444 (candidates 445 (lambda () 446 (lsp--catch 'input 447 (let ((lsp--throw-on-input lsp-completion-use-last-result) 448 (same-session? (and lsp-completion--cache 449 ;; Special case for empty prefix and empty result 450 (or (cl-second lsp-completion--cache) 451 (not (string-empty-p 452 (plist-get (cddr lsp-completion--cache) :prefix)))) 453 (equal (cl-first lsp-completion--cache) bounds-start) 454 (s-prefix? 455 (plist-get (cddr lsp-completion--cache) :prefix) 456 (buffer-substring-no-properties bounds-start (point)))))) 457 (cond 458 ((or done? result) result) 459 ((and (not lsp-completion-no-cache) 460 same-session? 461 (listp (cl-second lsp-completion--cache))) 462 (setf result (apply #'lsp-completion--filter-candidates 463 (cdr lsp-completion--cache)))) 464 (t 465 (-let* ((resp (lsp-request-while-no-input 466 "textDocument/completion" 467 (plist-put (lsp--text-document-position-params) 468 :context (lsp-completion--get-context trigger-chars same-session?)))) 469 (completed (and resp 470 (not (and (lsp-completion-list? resp) 471 (lsp:completion-list-is-incomplete resp))))) 472 (items (lsp--while-no-input 473 (--> (cond 474 ((lsp-completion-list? resp) 475 (lsp:completion-list-items resp)) 476 (t resp)) 477 (if (or completed 478 (seq-some #'lsp:completion-item-sort-text? it)) 479 (lsp-completion--sort-completions it) 480 it) 481 (-map (lambda (item) 482 (lsp-put item 483 :_emacsStartPoint 484 (or (lsp-completion--guess-prefix item) 485 bounds-start))) 486 it)))) 487 (markers (list bounds-start (copy-marker (point) t))) 488 (prefix (buffer-substring-no-properties bounds-start (point))) 489 (lsp-completion--no-reordering (not lsp-completion-sort-initial-results))) 490 (lsp-completion--clear-cache same-session?) 491 (setf done? completed 492 lsp-completion--cache (list bounds-start 493 (cond 494 ((and done? (not (seq-empty-p items))) 495 (lsp-completion--to-internal items)) 496 ((not done?) :incomplete)) 497 :lsp-items nil 498 :markers markers 499 :prefix prefix) 500 result (lsp-completion--filter-candidates 501 (cond (done? 502 (cl-second lsp-completion--cache)) 503 (lsp-completion-filter-on-incomplete 504 (lsp-completion--to-internal items))) 505 :lsp-items items 506 :markers markers 507 :prefix prefix)))))) 508 (:interrupted lsp-completion--last-result) 509 (`,res (setq lsp-completion--last-result res)))))) 510 (list 511 bounds-start 512 (point) 513 (lambda (probe pred action) 514 (cond 515 ((eq action 'metadata) 516 '(metadata (category . lsp-capf) 517 (display-sort-function . identity) 518 (cycle-sort-function . identity))) 519 ((eq (car-safe action) 'boundaries) nil) 520 (t 521 (complete-with-action action (funcall candidates) probe pred)))) 522 :annotation-function #'lsp-completion--annotate 523 :company-kind #'lsp-completion--candidate-kind 524 :company-deprecated #'lsp-completion--candidate-deprecated 525 :company-require-match 'never 526 :company-prefix-length 527 (save-excursion 528 (let ( 529 ;; 2 is a heuristic number to make sure we look futher back than 530 ;; the bounds-start, which can be different from the actual start 531 ;; of the symbol 532 (bounds-left (max (line-beginning-position) (- bounds-start 2))) 533 triggered-by-char?) 534 (while (and (> (point) bounds-left) 535 (not (equal (char-after) ?\s)) 536 (not triggered-by-char?)) 537 (setq triggered-by-char? (lsp-completion--looking-back-trigger-characterp trigger-chars)) 538 (goto-char (1- (point)))) 539 (and triggered-by-char? t))) 540 :company-match #'lsp-completion--company-match 541 :company-doc-buffer (-compose #'lsp-doc-buffer 542 #'lsp-completion--get-documentation) 543 :exit-function 544 (-rpartial #'lsp-completion--exit-fn candidates))))) 545 546 (defun lsp-completion--find-workspace (server-id) 547 (--first (eq (lsp--client-server-id (lsp--workspace-client it)) server-id) 548 (lsp-workspaces))) 549 550 (defun lsp-completion--exit-fn (candidate _status &optional candidates) 551 "Exit function of `completion-at-point'. 552 CANDIDATE is the selected completion item. 553 Others: CANDIDATES" 554 (unwind-protect 555 (-let* ((candidate (if (plist-member (text-properties-at 0 candidate) 556 'lsp-completion-item) 557 candidate 558 (cl-find candidate (funcall candidates) :test #'equal))) 559 ((&plist 'lsp-completion-item item 560 'lsp-completion-start-point start-point 561 'lsp-completion-markers markers 562 'lsp-completion-resolved resolved 563 'lsp-completion-prefix prefix) 564 (text-properties-at 0 candidate)) 565 ((&CompletionItem? :label :insert-text? :text-edit? :insert-text-format? 566 :additional-text-edits? :insert-text-mode? :command?) 567 ;; see #3498 typescript-language-server does not provide the 568 ;; proper insertText without resolving. 569 (if (and (lsp-completion--find-workspace 'ts-ls) 570 (not resolved)) 571 (lsp-completion--resolve item) 572 item))) 573 (cond 574 (text-edit? 575 (apply #'delete-region markers) 576 (insert prefix) 577 (pcase text-edit? 578 ((TextEdit) (lsp--apply-text-edit text-edit?)) 579 ((InsertReplaceEdit :insert :replace :new-text) 580 (lsp--apply-text-edit 581 (lsp-make-text-edit 582 :new-text new-text 583 :range (if (or (and current-prefix-arg (eq lsp-completion-default-behaviour :replace)) 584 (and (not current-prefix-arg) (eq lsp-completion-default-behaviour :insert))) 585 insert 586 replace)))))) 587 ((or (unless (lsp-falsy? insert-text?) insert-text?) label) 588 (apply #'delete-region markers) 589 (insert prefix) 590 (delete-region start-point (point)) 591 (insert (or (unless (lsp-falsy? insert-text?) insert-text?) label)))) 592 593 (lsp--indent-lines start-point (point) insert-text-mode?) 594 (when (equal insert-text-format? lsp/insert-text-format-snippet) 595 (lsp--expand-snippet (buffer-substring start-point (point)) 596 start-point 597 (point))) 598 599 (when lsp-completion-enable-additional-text-edit 600 (if (or (get-text-property 0 'lsp-completion-resolved candidate) 601 (not (seq-empty-p additional-text-edits?))) 602 (lsp--apply-text-edits additional-text-edits? 'completion) 603 (-let [(callback cleanup-fn) (lsp--create-apply-text-edits-handlers)] 604 (lsp-completion--resolve-async 605 item 606 (-compose callback #'lsp:completion-item-additional-text-edits?) 607 cleanup-fn)))) 608 609 (if (or (get-text-property 0 'lsp-completion-resolved candidate) 610 command?) 611 (when command? (lsp--execute-command command?)) 612 (lsp-completion--resolve-async 613 item 614 (-lambda ((&CompletionItem? :command?)) 615 (when command? (lsp--execute-command command?))))) 616 617 (when (and (or 618 (equal lsp-signature-auto-activate t) 619 (memq :after-completion lsp-signature-auto-activate) 620 (and (memq :on-trigger-char lsp-signature-auto-activate) 621 (-when-let ((&SignatureHelpOptions? :trigger-characters?) 622 (lsp--capability :signatureHelpProvider)) 623 (lsp-completion--looking-back-trigger-characterp 624 trigger-characters?)))) 625 (lsp-feature? "textDocument/signatureHelp")) 626 (lsp-signature-activate)) 627 628 (setq-local lsp-inhibit-lsp-hooks nil)) 629 (lsp-completion--clear-cache))) 630 631 (defun lsp-completion--regex-fuz (str) 632 "Build a regex sequence from STR. Insert .* between each char." 633 (apply #'concat 634 (cl-mapcar 635 #'concat 636 (cons "" (cdr (seq-map (lambda (c) (format "[^%c]*" c)) str))) 637 (seq-map (lambda (c) 638 (format "\\(%s\\)" (regexp-quote (char-to-string c)))) 639 str)))) 640 641 (defun lsp-completion--fuz-score (query str) 642 "Calculate fuzzy score for STR with query QUERY. 643 The return is nil or in range of (0, inf)." 644 (-when-let* ((md (cddr (or (get-text-property 0 'match-data str) 645 (let ((re (lsp-completion--regex-fuz query))) 646 (when (string-match re str) 647 (match-data)))))) 648 (start (pop md)) 649 (len (length str)) 650 ;; To understand how this works, consider these bad ascii(tm) 651 ;; diagrams showing how the pattern "foo" flex-matches 652 ;; "fabrobazo", "fbarbazoo" and "barfoobaz": 653 654 ;; f abr o baz o 655 ;; + --- + --- + 656 657 ;; f barbaz oo 658 ;; + ------ ++ 659 660 ;; bar foo baz 661 ;; --- +++ --- 662 663 ;; "+" indicates parts where the pattern matched. A "hole" in 664 ;; the middle of the string is indicated by "-". Note that there 665 ;; are no "holes" near the edges of the string. The completion 666 ;; score is a number bound by ]0..1]: the higher the better and 667 ;; only a perfect match (pattern equals string) will have score 668 ;; 1. The formula takes the form of a quotient. For the 669 ;; numerator, we use the number of +, i.e. the length of the 670 ;; pattern. For the denominator, it first computes 671 ;; 672 ;; hole_i_contrib = 1 + (Li-1)^1.05 for first hole 673 ;; hole_i_contrib = 1 + (Li-1)^0.25 for hole i of length Li 674 ;; 675 ;; The final value for the denominator is then given by: 676 ;; 677 ;; (SUM_across_i(hole_i_contrib) + 1) 678 ;; 679 (score-numerator 0) 680 (score-denominator 0) 681 (last-b -1) 682 (q-ind 0) 683 (update-score 684 (lambda (a b) 685 "Update score variables given match range (A B)." 686 (setq score-numerator (+ score-numerator (- b a))) 687 (unless (= a len) 688 ;; case mismatch will be pushed to near next rank 689 (unless (equal (aref query q-ind) (aref str a)) 690 (cl-incf a 0.9)) 691 (setq score-denominator 692 (+ score-denominator 693 (if (= a last-b) 0 694 (+ 1 (* (if (< 0 (- a last-b 1)) 1 -1) 695 (expt (abs (- a last-b 1)) 696 ;; Give a higher score for match near start 697 (if (eq last-b -1) 0.75 0.25)))))))) 698 (setq last-b b)))) 699 (while md 700 (funcall update-score start (cl-first md)) 701 ;; Due to the way completion regex is constructed, `(eq end (+ start 1))` 702 (cl-incf q-ind) 703 (pop md) 704 (setq start (pop md))) 705 (unless (zerop len) 706 (/ score-numerator (1+ score-denominator) 1.0)))) 707 708 (defun lsp-completion--fix-resolve-data (item) 709 "Patch `CompletionItem' ITEM for rust-analyzer otherwise resolve will fail. 710 See #2675" 711 (let ((data (lsp:completion-item-data? item))) 712 (when (lsp-member? data :import_for_trait_assoc_item) 713 (unless (lsp-get data :import_for_trait_assoc_item) 714 (lsp-put data :import_for_trait_assoc_item :json-false))))) 715 716 (defun lsp-completion--resolve (item) 717 "Resolve completion ITEM." 718 (cl-assert item nil "Completion item must not be nil") 719 (lsp-completion--fix-resolve-data item) 720 (or (ignore-errors 721 (when (lsp-feature? "completionItem/resolve") 722 (lsp-request "completionItem/resolve" 723 (lsp-delete (lsp-copy item) :_emacsStartPoint)))) 724 item)) 725 726 (defun lsp-completion--resolve-async (item callback &optional cleanup-fn) 727 "Resolve completion ITEM asynchronously with CALLBACK. 728 The CLEANUP-FN will be called to cleanup." 729 (cl-assert item nil "Completion item must not be nil") 730 (lsp-completion--fix-resolve-data item) 731 (ignore-errors 732 (if (lsp-feature? "completionItem/resolve") 733 (lsp-request-async "completionItem/resolve" 734 (lsp-delete (lsp-copy item) :_emacsStartPoint) 735 (lambda (result) 736 (funcall callback result) 737 (when cleanup-fn (funcall cleanup-fn))) 738 :error-handler (lambda (err) 739 (when cleanup-fn (funcall cleanup-fn)) 740 (error (lsp:json-error-message err))) 741 :cancel-handler cleanup-fn 742 :mode 'alive) 743 (funcall callback item) 744 (when cleanup-fn (funcall cleanup-fn))))) 745 746 747 ;;;###autoload 748 (defun lsp-completion--enable () 749 "Enable LSP completion support." 750 (when (and lsp-completion-enable 751 (lsp-feature? "textDocument/completion")) 752 (lsp-completion-mode 1))) 753 754 (defun lsp-completion--disable () 755 "Disable LSP completion support." 756 (lsp-completion-mode -1)) 757 758 (defun lsp-completion-passthrough-all-completions (_string table pred _point) 759 "Passthrough all completions from TABLE with PRED." 760 (defvar completion-lazy-hilit-fn) 761 (when (bound-and-true-p completion-lazy-hilit) 762 (setq completion-lazy-hilit-fn 763 (lambda (candidate) 764 (->> candidate 765 lsp-completion--company-match 766 (mapc (-lambda ((start . end)) 767 (put-text-property start end 'face 'completions-common-part candidate)))) 768 candidate))) 769 (all-completions "" table pred)) 770 771 ;;;###autoload 772 (define-minor-mode lsp-completion-mode 773 "Toggle LSP completion support." 774 :group 'lsp-completion 775 :global nil 776 :lighter "" 777 (let ((completion-started-fn (lambda (&rest _) 778 (setq-local lsp-inhibit-lsp-hooks t))) 779 (after-completion-fn (lambda (result) 780 (when (stringp result) 781 (lsp-completion--clear-cache)) 782 (setq-local lsp-inhibit-lsp-hooks nil)))) 783 (cond 784 (lsp-completion-mode 785 (make-local-variable 'completion-at-point-functions) 786 ;; Ensure that `lsp-completion-at-point' the first CAPF to be tried, 787 ;; unless user has put it elsewhere in the list by their own 788 (add-to-list 'completion-at-point-functions #'lsp-completion-at-point) 789 (make-local-variable 'completion-category-defaults) 790 (setf (alist-get 'lsp-capf completion-category-defaults) '((styles . (lsp-passthrough)))) 791 (make-local-variable 'completion-styles-alist) 792 (setf (alist-get 'lsp-passthrough completion-styles-alist) 793 '(completion-basic-try-completion 794 lsp-completion-passthrough-all-completions 795 "Passthrough completion.")) 796 797 (cond 798 ((equal lsp-completion-provider :none)) 799 ((and (not (equal lsp-completion-provider :none)) 800 (fboundp 'company-mode)) 801 (setq-local company-abort-on-unique-match nil) 802 (company-mode 1) 803 (setq-local company-backends (cl-adjoin 'company-capf company-backends :test #'equal))) 804 (t 805 (lsp--warn "Unable to autoconfigure company-mode."))) 806 807 (when (bound-and-true-p company-mode) 808 (add-hook 'company-completion-started-hook 809 completion-started-fn 810 nil 811 t) 812 (add-hook 'company-after-completion-hook 813 after-completion-fn 814 nil 815 t)) 816 (add-hook 'lsp-unconfigure-hook #'lsp-completion--disable nil t)) 817 (t 818 (remove-hook 'completion-at-point-functions #'lsp-completion-at-point t) 819 (setq-local completion-category-defaults 820 (cl-remove 'lsp-capf completion-category-defaults :key #'cl-first)) 821 (setq-local completion-styles-alist 822 (cl-remove 'lsp-passthrough completion-styles-alist :key #'cl-first)) 823 (remove-hook 'lsp-unconfigure-hook #'lsp-completion--disable t) 824 (when (featurep 'company) 825 (remove-hook 'company-completion-started-hook 826 completion-started-fn 827 t) 828 (remove-hook 'company-after-completion-hook 829 after-completion-fn 830 t)))))) 831 832 ;;;###autoload 833 (add-hook 'lsp-configure-hook (lambda () 834 (when (and lsp-auto-configure 835 lsp-completion-enable) 836 (lsp-completion--enable)))) 837 838 (lsp-consistency-check lsp-completion) 839 840 (provide 'lsp-completion) 841 ;;; lsp-completion.el ends here