config

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

lsp-completion.el (38181B)


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