config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

lsp-completion.el (38029B)


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