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