config

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

consult-lsp.el (27330B)


      1 ;;; consult-lsp.el --- LSP-mode Consult integration -*- lexical-binding: t; -*-
      2 
      3 ;; Licence: MIT
      4 ;; Keywords: tools, completion, lsp
      5 ;; Author: Gerry Agbobada
      6 ;; Maintainer: Gerry Agbobada
      7 ;; Package-Requires: ((emacs "27.1") (lsp-mode "5.0") (consult "0.16") (f "0.20.0"))
      8 ;; Version: 1.1-dev
      9 ;; Homepage: https://github.com/gagbo/consult-lsp
     10 
     11 ;; Copyright (c) 2021 Gerry Agbobada and contributors
     12 ;;
     13 ;; Permission is hereby granted, free of charge, to any person obtaining a copy
     14 ;; of this software and associated documentation files (the "Software"), to deal
     15 ;; in the Software without restriction, including without limitation the rights
     16 ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     17 ;; copies of the Software, and to permit persons to whom the Software is
     18 ;; furnished to do so, subject to the following conditions:
     19 ;;
     20 ;; The above copyright notice and this permission notice shall be included in all
     21 ;; copies or substantial portions of the Software.
     22 ;;
     23 ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     24 ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     25 ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     26 ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     27 ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     28 ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     29 ;; SOFTWARE.
     30 
     31 ;;; Commentary:
     32 ;; Provides LSP-mode related commands for consult
     33 ;;
     34 ;; The commands are autoloaded so you don't need to require anything to make them
     35 ;; available. Just use M-x and go!
     36 ;;
     37 ;;;; Diagnostics
     38 ;; M-x consult-lsp-diagnostics provides a view of all diagnostics in the current
     39 ;; workspace (or all workspaces if passed a prefix argument).
     40 ;;
     41 ;; You can use prefixes to filter diagnostics per severity, and
     42 ;; previewing/selecting a candidate will go to it directly.
     43 ;;
     44 ;;;; Symbols
     45 ;; M-x consult-lsp-symbols provides a selection/narrowing command to search
     46 ;; and go to any arbitrary symbol in the workspace (or all workspaces if
     47 ;; passed a prefix argument).
     48 ;;
     49 ;; You can use prefixes as well to filter candidates per type, and
     50 ;; previewing/selecting a candidate will go to it.
     51 ;;
     52 ;;;; File symbols
     53 ;; M-x consult-lsp-file-symbols provides a selection/narrowing command to search
     54 ;; and go to any arbitrary symbol in the selected buffer (like imenu)
     55 ;;
     56 ;;;; Contributions
     57 ;; Possible contributions for ameliorations include:
     58 ;; - using a custom format for the propertized candidates
     59 ;;   This should be done with :type 'function custom variables probably.
     60 ;; - checking the properties in LSP to see how diagnostic-sources should be used
     61 ;; - checking the properties in LSP to see how symbol-sources should be used
     62 ;;; Code:
     63 
     64 (eval-when-compile
     65   (require 'subr-x))
     66 (require 'consult)
     67 (require 'lsp)
     68 (require 'f)
     69 (require 'cl-lib)
     70 
     71 ;; Poorman's breaking change detection
     72 ;; The state protocol change happened in https://github.com/minad/consult/commit/3668df6afaa8c188d7c255fa6ae4e62d54cb20c9
     73 ;; which also happened to remove this macro
     74 (when (fboundp 'consult--with-location-upgrade)
     75   (user-error "This version of consult-lsp is unstable with older versions of consult. Please:
     76  - upgrade consult past https://github.com/minad/consult/commit/3668df6afaa8c188d7c255fa6ae4e62d54cb20c9 or
     77  - downgrade consult-lsp to 0.7 tag."))
     78 
     79 (defgroup consult-lsp nil
     80   "Consult commands for `lsp-mode'."
     81   :group 'tools
     82   :prefix "consult-lsp-")
     83 
     84 ;;;; Customization
     85 (defcustom consult-lsp-diagnostics-transformer-function #'consult-lsp--diagnostics--transformer
     86   "Function that transform LSP-mode diagnostic from a (file diag) pair
     87 to a candidate for `consult-lsp-diagnostics'."
     88   :type 'function
     89   :group 'consult-lsp)
     90 
     91 (defcustom consult-lsp-diagnostics-annotate-builder-function #'consult-lsp--diagnostics-annotate-builder
     92   "Annotation function builder for `consult-lsp-diagnostics'."
     93   :type 'function
     94   :group 'consult-lsp)
     95 
     96 (defcustom consult-lsp-symbols-transformer-function #'consult-lsp--symbols--transformer
     97   "Function that transform LSP symbols from symbol-info
     98 to a candidate for `consult-lsp-symbols'."
     99   :type 'function
    100   :group 'consult-lsp)
    101 
    102 (defcustom consult-lsp-symbols-annotate-builder-function #'consult-lsp--symbols-annotate-builder
    103   "Annotation function builder for `consult-lsp-symbols'."
    104   :type 'function
    105   :group 'consult-lsp)
    106 
    107 (defcustom consult-lsp-file-symbols-transformer-function #'consult-lsp--file-symbols--transformer
    108   "Function that transform LSP file symbols from symbol-info
    109  to a candidate for `consult-lsp-file-symbols'."
    110   :type 'function
    111   :group 'consult-lsp)
    112 
    113 (defcustom consult-lsp-file-symbols-annotate-builder-function #'consult-lsp--file-symbols-annotate-builder
    114   "Annotation function builder for `consult-lsp-file-symbols'."
    115   :type 'function
    116   :group 'consult-lsp)
    117 
    118 (defcustom consult-lsp-symbols-narrow
    119   '(
    120     ;; Lowercase classes
    121     (?c . "Class")
    122     (?f . "Field")
    123     (?e . "Enum")
    124     (?i . "Interface")
    125     (?m . "Module")
    126     (?n . "Namespace")
    127     (?p . "Package")
    128     (?s . "Struct")
    129     (?t . "Type Parameter")
    130     (?v . "Variable")
    131 
    132     ;; Uppercase classes
    133     (?A . "Array")
    134     (?B . "Boolean")
    135     (?C . "Constant")
    136     (?E . "Enum Member")
    137     (?F . "Function")
    138     (?M . "Method")
    139     (?N . "Number")
    140     (?O . "Object")
    141     (?P . "Property")
    142     (?S . "String")
    143 
    144     (?o . "Other"))
    145     ;; Example types included in "Other" (i.e. the ignored)
    146     ;; (?n . "Null")
    147     ;; (?c . "Constructor")
    148     ;; (?e . "Event")
    149     ;; (?k . "Key")
    150     ;; (?o . "Operator")
    151     
    152   "Set of narrow keys for `consult-lsp-symbols' and `consult-lsp-file-symbols'.
    153 
    154 It MUST have a \"Other\" category for everything that is not listed."
    155   :group 'consult-lsp
    156   :type '(alist :key-type character :value-type string))
    157 
    158 (defcustom consult-lsp-min-query-length consult-async-min-input
    159   "Don't query LSP server with fewer than this many characters of input."
    160   :type 'integer
    161   :group 'consult-lsp)
    162 
    163 
    164 
    165 ;;;; Inner functions
    166 ;; These helpers are mostly verbatim copies of consult internal functions that are needed,
    167 ;; but are unstable and would break if relied upon
    168 (defun consult-lsp--marker-from-line-column (buffer line column)
    169   "Get marker in BUFFER from LINE and COLUMN."
    170   (when (buffer-live-p buffer)
    171     (with-current-buffer buffer
    172       (save-restriction
    173         (save-excursion
    174           (widen)
    175           (goto-char (point-min))
    176           ;; Location data might be invalid by now!
    177           (ignore-errors
    178             (forward-line (1- line))
    179             (forward-char column))
    180           (point-marker))))))
    181 
    182 (defun consult-lsp--format-file-line-match (file line &optional match)
    183   "Format string FILE:LINE:MATCH with faces."
    184   (setq line (number-to-string line)
    185         match (concat file ":" line (and match ":") match)
    186         file (length file))
    187   (put-text-property 0 file 'face 'consult-file match)
    188   (put-text-property (1+ file) (+ 1 file (length line)) 'face 'consult-line-number match)
    189   match)
    190 
    191 
    192 ;;;; Diagnostics
    193 
    194 (defun consult-lsp--diagnostics--flatten-diagnostics (transformer &optional current-workspace?)
    195   "Flatten the list of LSP-mode diagnostics to consult candidates.
    196 
    197 TRANSFORMER takes (file diag) and returns a suitable element for
    198 `consult--read'.
    199 CURRENT-WORKSPACE? has the same meaning as in `lsp-diagnostics'."
    200   (sort
    201    (flatten-list
    202     (ht-map
    203      (lambda (file diags)
    204        (mapcar (lambda (diag) (funcall transformer file diag))
    205                diags))
    206      (lsp-diagnostics current-workspace?)))
    207    ;; Sort by ascending severity
    208    (lambda (cand-left cand-right)
    209      (let* ((diag-left (cdr (get-text-property 0 'consult--candidate cand-left)))
    210             (diag-right (cdr (get-text-property 0 'consult--candidate cand-right)))
    211             (sev-left (or (lsp:diagnostic-severity? diag-left) 12))
    212             (sev-right (or (lsp:diagnostic-severity? diag-right) 12)))
    213        (< sev-left sev-right)))))
    214 
    215 (defun consult-lsp--diagnostics--severity-to-level (diag)
    216   "Convert diagnostic severity of DIAG to a string."
    217   (pcase (lsp:diagnostic-severity? diag)
    218     (1 (propertize "error" 'face 'error))
    219     (2 (propertize "warn" 'face 'warning))
    220     (3 (propertize "info" 'face 'success))
    221     (4 (propertize "hint" 'face 'italic))
    222     (_ "unknown")))
    223 
    224 (defconst consult-lsp--diagnostics--narrow
    225   '((?e . "Errors")
    226     (?w . "Warnings")
    227     (?i . "Infos")
    228     (?h . "Hints")
    229     (?u . "Unknown"))
    230   "Set of narrow keys for `consult-lsp-diagnostics'.")
    231 
    232 (defun consult-lsp--diagnostics--severity-to-type (diag)
    233   "Convert diagnostic severity of DIAG to a type for consult--type."
    234   (pcase (lsp:diagnostic-severity? diag)
    235     (1 (car (rassoc "Errors" consult-lsp--diagnostics--narrow)))
    236     (2 (car (rassoc "Warnings" consult-lsp--diagnostics--narrow)))
    237     (3 (car (rassoc "Infos" consult-lsp--diagnostics--narrow)))
    238     (4 (car (rassoc "Hints" consult-lsp--diagnostics--narrow)))
    239     (_ (car (rassoc "Unknown" consult-lsp--diagnostics--narrow)))))
    240 
    241 (defun consult-lsp--diagnostics--source (diag)
    242   "Convert source of DIAG to a propertized string."
    243   (propertize (lsp:diagnostic-source? diag) 'face 'success))
    244 
    245 (defun consult-lsp--diagnostics--diagnostic-marker (file diag)
    246   "Return a marker in FILE at the beginning of DIAG."
    247   (consult-lsp--marker-from-line-column
    248    file
    249    (lsp-translate-line (1+ (lsp:position-line (lsp:range-start (lsp:diagnostic-range diag)))))
    250    (lsp-translate-column (1+ (lsp:position-character (lsp:range-start (lsp:diagnostic-range diag)))))))
    251 
    252 (defun consult-lsp--diagnostics--transformer (file diag)
    253   "Transform LSP-mode diagnostics from a pair FILE DIAG to a candidate."
    254   (propertize
    255    (format "%-60.60s"
    256            (consult-lsp--format-file-line-match
    257             (if-let ((wks (lsp-workspace-root file)))
    258                 (f-relative file wks)
    259               file)
    260             (lsp-translate-line (1+ (lsp-get (lsp-get (lsp-get diag :range) :start) :line)))))
    261    'consult--candidate (cons file diag)
    262    'consult--type (consult-lsp--diagnostics--severity-to-type diag)))
    263 
    264 (defun consult-lsp--diagnostics-annotate-builder ()
    265   "Annotation function for `consult-lsp-diagnostics'.
    266 
    267 See `consult-lsp--diagnostics--transformer' for the usable text-properties
    268 in candidates."
    269   (let* ((width (length (number-to-string (line-number-at-pos
    270                                            (point-max)
    271                                            consult-line-numbers-widen)))))
    272     (lambda (cand)
    273       (let* ((diag (cdr (get-text-property 0 'consult--candidate cand))))
    274         (list cand
    275               (format "%-5s " (consult-lsp--diagnostics--severity-to-level diag))
    276               (concat
    277                (format "%s" (lsp:diagnostic-message diag))
    278                (when-let ((source (consult-lsp--diagnostics--source diag)))
    279                  (propertize (format " - %s" source) 'face 'font-lock-doc-face))))))))
    280 
    281 (defun consult-lsp--diagnostics--state ()
    282   "LSP diagnostic preview."
    283   (let ((open (consult--temporary-files))
    284         (jump (consult--jump-state)))
    285     (lambda (action cand)
    286       (when (eq action 'exit)
    287         (funcall open))
    288       (funcall jump action
    289                (when cand (consult-lsp--marker-from-line-column
    290                            (and (car cand) (funcall (if (eq action 'finish) #'find-file open) (car cand)))
    291                            (lsp-translate-line (1+ (lsp:position-line (lsp:range-start (lsp:diagnostic-range (cdr cand))))))
    292                            (lsp-translate-column (1+ (lsp:position-character (lsp:range-start (lsp:diagnostic-range (cdr cand))))))))))))
    293 
    294 ;;;###autoload
    295 (defun consult-lsp-diagnostics (arg)
    296   "Query LSP-mode diagnostics.
    297 
    298 When ARG is set through prefix, query all workspaces."
    299   (interactive "P")
    300   (let ((all-workspaces? arg))
    301     (consult--read (consult-lsp--diagnostics--flatten-diagnostics consult-lsp-diagnostics-transformer-function (not all-workspaces?))
    302                    :prompt (concat  "LSP Diagnostics " (when arg "(all workspaces) "))
    303                    :annotate (funcall consult-lsp-diagnostics-annotate-builder-function)
    304                    :require-match t
    305                    :history t
    306                    :category 'consult-lsp-diagnostics
    307                    :sort nil
    308                    :group (consult--type-group consult-lsp--diagnostics--narrow)
    309                    :narrow (consult--type-narrow consult-lsp--diagnostics--narrow)
    310                    :state (consult-lsp--diagnostics--state)
    311                    :lookup #'consult--lookup-candidate)))
    312 
    313 
    314 ;;;; Symbols
    315 
    316 (defun consult-lsp--symbols--kind-to-narrow (symbol-info)
    317   "Get the narrow character for SYMBOL-INFO."
    318   (if-let ((pair (rassoc
    319                   (alist-get (lsp:symbol-information-kind symbol-info) lsp-symbol-kinds)
    320                   consult-lsp-symbols-narrow)))
    321       (car pair)
    322     (rassoc "Other" consult-lsp-symbols-narrow)))
    323 
    324 (defun consult-lsp--symbols--state ()
    325   "Return a LSP symbol preview function."
    326   (let ((open (consult--temporary-files))
    327         (jump (consult--jump-state)))
    328     (lambda (action cand)
    329       (when (eq action 'exit)
    330         (funcall open))
    331       (funcall jump action
    332                (when cand (let* ((location (lsp:symbol-information-location cand))
    333                                  (uri (lsp:location-uri location)))
    334                             (consult-lsp--marker-from-line-column
    335                              (and uri (funcall (if (eq action 'finish) #'find-file open) (lsp--uri-to-path uri)))
    336                              (thread-first location
    337                                            (lsp:location-range)
    338                                            (lsp:range-start)
    339                                            (lsp:position-line)
    340                                            (1+)
    341                                            (lsp-translate-line))
    342                              (thread-first location
    343                                            (lsp:location-range)
    344                                            (lsp:range-start)
    345                                            (lsp:position-character)
    346                                            (1+)
    347                                            (lsp-translate-column)))))))))
    348 
    349 ;; It is an async source because some servers, like rust-analyzer, send a
    350 ;; max count of results for queries (120 last time checked). Therefore, in
    351 ;; big projects the first query might not have the target result to filter on.
    352 ;; To avoid this issue, we use an async source that retriggers the request.
    353 (defun consult-lsp--symbols--make-async-source (async workspaces)
    354   "Pipe a `consult--read' compatible async-source ASYNC to search for symbols in WORKSPACES."
    355   (let* ((async (consult--async-indicator async))
    356          (cancel-token :consult-lsp--symbols)
    357          (query-lsp (lambda (query)
    358                       (with-lsp-workspaces workspaces
    359                         (consult--async-log "consult-lsp-symbols request started for %S\n" query)
    360                         (funcall async 'indicator 'running)
    361                         (lsp-request-async "workspace/symbol"
    362                                            (list :query query)
    363                                            (lambda (res)
    364                                              ;; Flush old candidates list
    365                                              (funcall async 'flush)
    366                                              (funcall async res)
    367                                              (funcall async 'indicator 'finished))
    368                                            :mode 'detached
    369                                            :no-merge t
    370                                            :cancel-token cancel-token)))))
    371     (lambda (action)
    372       (pcase-exhaustive action
    373         ('setup
    374          (funcall async action)
    375          (funcall query-lsp ""))
    376         ((pred stringp)
    377          (funcall async action)
    378          (unless (string= "" action)
    379            (funcall query-lsp action)))
    380         ('destroy
    381          (funcall async action)
    382          (lsp-cancel-request-by-token cancel-token))
    383         (_ (funcall async action))))))
    384 
    385 (defun consult-lsp--symbols--transformer (workspace symbol-info)
    386   "Default transformer to produce a completion candidate from the WORKSPACE's SYMBOL-INFO."
    387   (propertize
    388    ;; We have to add the location in the candidate string for 2 purposes,
    389    ;; in case symbols have the same name:
    390    ;; - being able to narrow using the path
    391    ;; - because it breaks marginalia integration otherwise
    392    ;;   (it uses a cache where candidates are caching keys through `marginalia--cached')
    393    (format "%s — %s"
    394            (lsp:symbol-information-name symbol-info)
    395            (consult-lsp--format-file-line-match
    396             (let ((file
    397                    (lsp--uri-to-path (lsp:location-uri (lsp:symbol-information-location symbol-info)))))
    398               (if-let ((wks (lsp--workspace-root workspace)))
    399                   (f-relative file wks)
    400                 file))
    401             (thread-first symbol-info
    402                           (lsp:symbol-information-location)
    403                           (lsp:location-range)
    404                           (lsp:range-start)
    405                           (lsp:position-line)
    406                           (1+)
    407                           (lsp-translate-line))))
    408    'consult--type (consult-lsp--symbols--kind-to-narrow symbol-info)
    409    'consult--candidate symbol-info
    410    'consult--details (lsp:document-symbol-detail? symbol-info)
    411    'consult--container-name (lsp-get symbol-info :containerName)))
    412 
    413 (defun consult-lsp--symbols--make-transformer (workspace-symbols-info)
    414   "Invokes the default transformer for each SYMBOL-INFO in the response.
    415 
    416 WORKSPACE-SYMBOLS-INFO is of the form (lsp--workspace . (list symbol-info))"
    417   (let ((workspace (car workspace-symbols-info))
    418         (symbols-info (cdr workspace-symbols-info)))
    419     (mapcar (lambda (symbol-info)
    420               (funcall consult-lsp-symbols-transformer-function workspace symbol-info))
    421             symbols-info)))
    422 
    423 (defun consult-lsp--symbols-annotate-builder ()
    424   "Annotation function for `consult-lsp-symbols'.
    425 
    426 See `consult-lsp--symbols--transformer' for the available text-properties
    427 usable in the annotation-function."
    428   (let* ((width (length (number-to-string (line-number-at-pos
    429                                            (point-max)
    430                                            consult-line-numbers-widen))))
    431          (fmt (propertize (format "%%%dd " width) 'face 'consult-line-number-prefix)))
    432     (lambda (cand)
    433       (let* ((symbol-info (get-text-property 0 'consult--candidate cand))
    434              (line (thread-first symbol-info
    435                                  (lsp:symbol-information-location)
    436                                  (lsp:location-range)
    437                                  (lsp:range-start)
    438                                  (lsp:position-line)
    439                                  (1+)
    440                                  (lsp-translate-line))))
    441         (list
    442          cand
    443          (format "%-10s "
    444                  (alist-get (lsp:symbol-information-kind symbol-info) lsp-symbol-kinds))
    445          (concat
    446           (or
    447            (when-let ((details (get-text-property 0 'consult--details cand)))
    448              (propertize (format " — %s" details) 'face 'font-lock-doc-face))
    449            (when-let ((cont (get-text-property 0 'consult--container-name cand)))
    450              (propertize (format " %s" cont) 'face 'completions-annotations))
    451            "")))))))
    452 
    453 ;;;###autoload
    454 (defun consult-lsp-symbols (arg)
    455   "Query workspace symbols. When ARG is set through prefix, query all workspaces."
    456   (interactive "P")
    457   (let* ((initial "")
    458          (all-workspaces? arg)
    459          (ws (or (and all-workspaces? (-uniq (-flatten (ht-values (lsp-session-folder->servers (lsp-session))))))
    460                  (lsp-workspaces)
    461                  (lsp-get (lsp-session-folder->servers (lsp-session))
    462                           (lsp-workspace-root default-directory))))
    463          (consult-async-min-input consult-lsp-min-query-length))
    464     (unless ws
    465       (user-error "There is no active workspace !"))
    466     (consult--read
    467      (thread-first
    468        (consult--async-sink)
    469        (consult--async-refresh-timer)
    470        (consult--async-transform mapcan #'consult-lsp--symbols--make-transformer)
    471        (consult-lsp--symbols--make-async-source ws)
    472        (consult--async-throttle)
    473        (consult--async-split))
    474      :prompt "LSP Symbols "
    475      :annotate (funcall consult-lsp-symbols-annotate-builder-function)
    476      :require-match t
    477      :history t
    478      :add-history (consult--async-split-thingatpt 'symbol)
    479      :initial (consult--async-split-initial initial)
    480      :category 'consult-lsp-symbols
    481      :lookup #'consult--lookup-candidate
    482      :group (consult--type-group consult-lsp-symbols-narrow)
    483      :narrow (consult--type-narrow consult-lsp-symbols-narrow)
    484      :state (consult-lsp--symbols--state))))
    485 
    486 
    487 ;;;; File symbols
    488 
    489 (defun consult-lsp--flatten-document-symbols (to-flatten)
    490   "Helper function for flattening document symbols TO-FLATTEN to a plain list."
    491   (cl-labels ((rec-helper
    492                 (to-flatten accumulator parents)
    493                (dolist (table to-flatten)
    494                  ;; Table may be of type SymbolInformation or DocumentSymbol. See
    495                  ;; https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol
    496                  ;; SymbolInformation already has containerName, but one have to
    497                  ;; recreate it when DocumentSymbol has returned.
    498                  (when (and parents
    499                             (null (lsp-get table :containerName)))
    500                    (lsp-put table :containerName (string-join parents ".")))
    501                  (push table accumulator)
    502                  (when-let ((children (lsp-get table :children)))
    503                    (setq parents (append parents (list (lsp-get table :name))))
    504                    (setq accumulator (rec-helper
    505                                       (append children nil) ; convert children from vector to list
    506                                       accumulator
    507                                       parents))
    508                    (setq parents (nbutlast parents))))
    509                accumulator))
    510     (nreverse (rec-helper to-flatten nil nil))))
    511 
    512 (defun consult-lsp--file-symbols--transformer (symbol)
    513   "Default transformer to produce a completion candidate from SYMBOL."
    514   (let ((lbeg (thread-first symbol
    515                             (lsp:document-symbol-selection-range)
    516                             (lsp:range-start)
    517                             (lsp:position-line)
    518                             (lsp-translate-line)))
    519         (lend (thread-first symbol
    520                             (lsp:document-symbol-selection-range)
    521                             (lsp:range-end)
    522                             (lsp:position-line)
    523                             (lsp-translate-line)))
    524         (cbeg (thread-first symbol
    525                             (lsp:document-symbol-selection-range)
    526                             (lsp:range-start)
    527                             (lsp:position-character)
    528                             (lsp-translate-column)))
    529         (cend (thread-first symbol
    530                             (lsp:document-symbol-selection-range)
    531                             (lsp:range-end)
    532                             (lsp:position-character)
    533                             (lsp-translate-column))))
    534     (let ((beg (lsp--line-character-to-point lbeg cbeg))
    535           (end (lsp--line-character-to-point lend cend))
    536           (marker (make-marker)))
    537       (set-marker marker beg)
    538       ;; Pre-condition to respect narrowing
    539       (unless (or (< beg (point-min))
    540                   (> end (point-max)))
    541         ;; NOTE: no need to add anything to the candidate string like
    542         ;; for consult-lsp-symbols because
    543         ;; - we have the line location and there are less hits in this command,
    544         ;; - the candidates are different caching keys because of
    545         ;;   `consult--location-candidate' usage.
    546         ;;
    547         ;; `consult--location-candidate' is unavailable for
    548         ;; `consult-lsp--symbols--transformer'because it needs a marker,
    549         ;; and we cannot create marker for buffers that aren't open.
    550         (consult--location-candidate
    551          (let ((substr (consult--buffer-substring beg end 'fontify))
    552                (symb-info-name (lsp:symbol-information-name symbol)))
    553            (concat substr
    554                    (unless (string= substr symb-info-name)
    555                      (format " (%s)"
    556                              symb-info-name))))
    557          marker
    558          (1+ lbeg)
    559          marker
    560          'consult--type (consult-lsp--symbols--kind-to-narrow symbol)
    561          'consult--name (lsp:symbol-information-name symbol)
    562          'consult--details (lsp:document-symbol-detail? symbol)
    563          'consult--container-name (lsp-get symbol :containerName))))))
    564 
    565 (defun consult-lsp--file-symbols-candidates ()
    566   "Returns all candidates for a `consult-lsp-file-symbols' search.
    567 
    568 See the :annotate documentation of `consult--read' for more information."
    569   (consult--forbid-minibuffer)
    570   (let* ((all-symbols (consult-lsp--flatten-document-symbols
    571                        (lsp-request "textDocument/documentSymbol"
    572                                     (lsp-make-document-symbol-params :text-document
    573                                                                      (lsp--text-document-identifier)))))
    574          (candidates (mapcar consult-lsp-file-symbols-transformer-function all-symbols)))
    575     (unless candidates
    576       (user-error "No symbols"))
    577     candidates))
    578 
    579 (defun consult-lsp--file-symbols-annotate-builder ()
    580   "Annotation function for `consult-lsp-file-symbols'."
    581   (let* ((width (length (number-to-string (line-number-at-pos
    582                                            (point-max)
    583                                            consult-line-numbers-widen))))
    584          (fmt (propertize (format "%%%dd " width) 'face 'consult-line-number-prefix)))
    585     (lambda (cand)
    586       (let ((line (cdr (get-text-property 0 'consult-location cand))))
    587         (list cand
    588               (format fmt line)
    589               (concat
    590                (when-let ((details (get-text-property 0 'consult--details cand)))
    591                  (propertize (format " - %s" details) 'face 'font-lock-doc-face))
    592                (when-let ((container (get-text-property 0 'consult--container-name cand)))
    593                  (propertize (format " %s" container) 'face 'completions-annotations))))))))
    594 
    595 ;;;###autoload
    596 (defun consult-lsp-file-symbols (group-results)
    597   "Search symbols defined in current file in a manner similar to `consult-line'.
    598 
    599 If the prefix argument GROUP-RESULTS is specified, symbols are grouped by their
    600 kind; otherwise they are returned in the order that they appear in the file."
    601   (interactive "P")
    602   (consult--read
    603    (consult--with-increased-gc (consult-lsp--file-symbols-candidates))
    604    :prompt "Go to symbol: "
    605    :annotate (funcall consult-lsp-file-symbols-annotate-builder-function)
    606    :require-match t
    607    :sort nil
    608    :history '(:input consult--line-history)
    609    :category 'consult-lsp-file-symbols
    610    :lookup #'consult--line-match
    611    :narrow (consult--type-narrow consult-lsp-symbols-narrow)
    612    :group (when group-results (consult--type-group consult-lsp-symbols-narrow))
    613    :state (consult--jump-state)))
    614 
    615 
    616 
    617 (provide 'consult-lsp)
    618 ;;; consult-lsp.el ends here