lsp-mode.el (427830B)
1 ;;; lsp-mode.el --- LSP mode -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020-2024 emacs-lsp maintainers 4 5 ;; Author: Vibhav Pant, Fangrui Song, Ivan Yonchovski 6 ;; Keywords: languages 7 ;; Package-Requires: ((emacs "27.1") (dash "2.18.0") (f "0.20.0") (ht "2.3") (spinner "1.7.3") (markdown-mode "2.3") (lv "0") (eldoc "1.11")) 8 ;; Version: 9.0.1 9 10 ;; URL: https://github.com/emacs-lsp/lsp-mode 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;; Emacs client/library for the Language Server Protocol 27 28 ;;; Code: 29 30 (require 'cl-generic) 31 (require 'cl-lib) 32 (require 'compile) 33 (require 'dash) 34 (require 'epg) 35 (require 'ewoc) 36 (require 'f) 37 (require 'filenotify) 38 (require 'files) 39 (require 'ht) 40 (require 'imenu) 41 (require 'inline) 42 (require 'json) 43 (require 'lv) 44 (require 'markdown-mode) 45 (require 'network-stream) 46 (require 'pcase) 47 (require 'rx) 48 (require 's) 49 (require 'seq) 50 (require 'spinner) 51 (require 'subr-x) 52 (require 'tree-widget) 53 (require 'url-parse) 54 (require 'url-util) 55 (require 'widget) 56 (require 'xref) 57 (require 'minibuffer) 58 (require 'help-mode) 59 (require 'lsp-protocol) 60 61 (defgroup lsp-mode nil 62 "Language Server Protocol client." 63 :group 'tools 64 :tag "Language Server (lsp-mode)") 65 66 (declare-function evil-set-command-property "ext:evil-common") 67 (declare-function projectile-project-root "ext:projectile") 68 (declare-function yas-expand-snippet "ext:yasnippet") 69 (declare-function dap-mode "ext:dap-mode") 70 (declare-function dap-auto-configure-mode "ext:dap-mode") 71 72 (defvar yas-inhibit-overlay-modification-protection) 73 (defvar yas-indent-line) 74 (defvar yas-wrap-around-region) 75 (defvar yas-also-auto-indent-first-line) 76 (defvar dap-auto-configure-mode) 77 (defvar dap-ui-menu-items) 78 (defvar company-minimum-prefix-length) 79 80 (defconst lsp--message-type-face 81 `((1 . ,compilation-error-face) 82 (2 . ,compilation-warning-face) 83 (3 . ,compilation-message-face) 84 (4 . ,compilation-info-face))) 85 86 (defconst lsp--errors 87 '((-32700 "Parse Error") 88 (-32600 "Invalid Request") 89 (-32601 "Method not Found") 90 (-32602 "Invalid Parameters") 91 (-32603 "Internal Error") 92 (-32099 "Server Start Error") 93 (-32000 "Server End Error") 94 (-32002 "Server Not Initialized") 95 (-32001 "Unknown Error Code") 96 (-32800 "Request Cancelled")) 97 "Alist of error codes to user friendly strings.") 98 99 (defconst lsp--empty-ht (make-hash-table)) 100 101 (eval-and-compile 102 (defun dash-expand:&lsp-wks (key source) 103 `(,(intern-soft (format "lsp--workspace-%s" (eval key))) ,source)) 104 105 (defun dash-expand:&lsp-cln (key source) 106 `(,(intern-soft (format "lsp--client-%s" (eval key))) ,source))) 107 108 (define-obsolete-variable-alias 'lsp-print-io 'lsp-log-io "lsp-mode 6.1") 109 110 (defcustom lsp-log-io nil 111 "If non-nil, log all messages from the language server to a *lsp-log* buffer." 112 :group 'lsp-mode 113 :type 'boolean) 114 115 (defcustom lsp-log-io-allowlist-methods '() 116 "The methods to filter before print to lsp-log-io." 117 :group 'lsp-mode 118 :type '(repeat string) 119 :package-version '(lsp-mode . "9.0.0")) 120 121 (defcustom lsp-log-max message-log-max 122 "Maximum number of lines to keep in the log buffer. 123 If nil, disable message logging. If t, log messages but don’t truncate 124 the buffer when it becomes large." 125 :group 'lsp-mode 126 :type '(choice (const :tag "Disable" nil) 127 (integer :tag "lines") 128 (const :tag "Unlimited" t)) 129 :package-version '(lsp-mode . "6.1")) 130 131 (defcustom lsp-io-messages-max t 132 "Maximum number of messages that can be locked in a `lsp-io' buffer." 133 :group 'lsp-mode 134 :type '(choice (const :tag "Unlimited" t) 135 (integer :tag "Messages")) 136 :package-version '(lsp-mode . "6.1")) 137 138 (defcustom lsp-keep-workspace-alive t 139 "If non nil keep workspace alive when the last workspace buffer is closed." 140 :group 'lsp-mode 141 :type 'boolean) 142 143 (defcustom lsp-enable-snippet t 144 "Enable/disable snippet completion support." 145 :group 'lsp-completion 146 :type 'boolean) 147 148 (defcustom lsp-enable-folding t 149 "Enable/disable code folding support." 150 :group 'lsp-mode 151 :type 'boolean 152 :package-version '(lsp-mode . "6.1")) 153 154 (define-obsolete-variable-alias 'lsp-enable-semantic-highlighting 'lsp-semantic-tokens-enable "lsp-mode 8.0.0") 155 156 (defcustom lsp-semantic-tokens-enable nil 157 "Enable/disable support for semantic tokens. 158 As defined by the Language Server Protocol 3.16." 159 :group 'lsp-semantic-tokens 160 :type 'boolean) 161 162 (defcustom lsp-folding-range-limit nil 163 "The maximum number of folding ranges to receive from the language server." 164 :group 'lsp-mode 165 :type '(choice (const :tag "No limit." nil) 166 (integer :tag "Number of lines.")) 167 :package-version '(lsp-mode . "6.1")) 168 169 (defcustom lsp-folding-line-folding-only nil 170 "If non-nil, only fold complete lines." 171 :group 'lsp-mode 172 :type 'boolean 173 :package-version '(lsp-mode . "6.1")) 174 175 (defcustom lsp-client-packages 176 '( ccls lsp-actionscript lsp-ada lsp-angular lsp-ansible lsp-asm lsp-astro 177 lsp-autotools lsp-awk lsp-bash lsp-beancount lsp-bufls lsp-clangd 178 lsp-clojure lsp-cmake lsp-cobol lsp-credo lsp-crystal lsp-csharp lsp-css 179 lsp-cucumber lsp-cypher lsp-d lsp-dart lsp-dhall lsp-docker lsp-dockerfile 180 lsp-elixir lsp-elm lsp-emmet lsp-erlang lsp-eslint lsp-fortran lsp-fsharp 181 lsp-gdscript lsp-gleam lsp-glsl lsp-go lsp-golangci-lint lsp-grammarly 182 lsp-graphql lsp-groovy lsp-hack lsp-haskell lsp-haxe lsp-idris lsp-java 183 lsp-javascript lsp-jq lsp-json lsp-kotlin lsp-latex lsp-lisp lsp-ltex 184 lsp-lua lsp-magik lsp-markdown lsp-marksman lsp-mdx lsp-meson lsp-metals lsp-mint 185 lsp-mojo lsp-move lsp-mssql lsp-nginx lsp-nim lsp-nix lsp-nushell lsp-ocaml 186 lsp-openscad lsp-pascal lsp-perl lsp-perlnavigator lsp-php lsp-pls 187 lsp-purescript lsp-pwsh lsp-pyls lsp-pylsp lsp-pyright lsp-python-ms 188 lsp-qml lsp-r lsp-racket lsp-remark lsp-rf lsp-rubocop lsp-ruby-lsp 189 lsp-ruby-syntax-tree lsp-ruff-lsp lsp-rust lsp-semgrep lsp-shader 190 lsp-solargraph lsp-solidity lsp-sonarlint lsp-sorbet lsp-sourcekit 191 lsp-sql lsp-sqls lsp-steep lsp-svelte lsp-tailwindcss lsp-terraform 192 lsp-tex lsp-tilt lsp-toml lsp-trunk lsp-ttcn3 lsp-typeprof lsp-v 193 lsp-vala lsp-verilog lsp-vetur lsp-vhdl lsp-vimscript lsp-volar lsp-wgsl 194 lsp-xml lsp-yaml lsp-yang lsp-zig) 195 "List of the clients to be automatically required." 196 :group 'lsp-mode 197 :type '(repeat symbol)) 198 199 (defcustom lsp-progress-via-spinner t 200 "If non-nil, display LSP $/progress reports via a spinner in the modeline." 201 :group 'lsp-mode 202 :type 'boolean) 203 204 (defcustom lsp-progress-spinner-type 'progress-bar 205 "Holds the type of spinner to be used in the mode-line. 206 Takes a value accepted by `spinner-start'." 207 :group 'lsp-mode 208 :type `(choice :tag "Choose a spinner by name" 209 ,@(mapcar (lambda (c) (list 'const (car c))) 210 spinner-types))) 211 212 (defvar-local lsp-use-workspace-root-for-server-default-directory nil 213 "Use `lsp-workspace-root' for `default-directory' when starting LSP process.") 214 215 (defvar-local lsp--cur-workspace nil) 216 217 (defvar-local lsp--cur-version 0) 218 (defvar-local lsp--virtual-buffer-connections nil) 219 (defvar-local lsp--virtual-buffer nil) 220 (defvar lsp--virtual-buffer-mappings (ht)) 221 222 (defvar lsp--uri-file-prefix (pcase system-type 223 (`windows-nt "file:///") 224 (_ "file://")) 225 "Prefix for a file-uri.") 226 227 (defvar-local lsp-buffer-uri nil 228 "If set, return it instead of calculating it using `buffer-file-name'.") 229 230 (define-error 'lsp-error "Unknown lsp-mode error") 231 (define-error 'lsp-empty-response-error 232 "Empty response from the language server" 'lsp-error) 233 (define-error 'lsp-timed-out-error 234 "Timed out while waiting for a response from the language server" 'lsp-error) 235 (define-error 'lsp-capability-not-supported 236 "Capability not supported by the language server" 'lsp-error) 237 (define-error 'lsp-file-scheme-not-supported 238 "Unsupported file scheme" 'lsp-error) 239 (define-error 'lsp-client-already-exists-error 240 "A client with this server-id already exists" 'lsp-error) 241 (define-error 'lsp-no-code-actions 242 "No code actions" 'lsp-error) 243 244 (defcustom lsp-auto-guess-root nil 245 "Automatically guess the project root using projectile/project. 246 Do *not* use this setting unless you are familiar with `lsp-mode' 247 internals and you are sure that all of your projects are 248 following `projectile'/`project.el' conventions." 249 :group 'lsp-mode 250 :type 'boolean) 251 252 (defcustom lsp-guess-root-without-session nil 253 "Ignore the session file when calculating the project root. 254 You almost always want to set lsp-auto-guess-root too. 255 Do *not* use this setting unless you are familiar with `lsp-mode' 256 internals and you are sure that all of your projects are 257 following `projectile'/`project.el' conventions." 258 :group 'lsp-mode 259 :type 'boolean) 260 261 (defcustom lsp-restart 'interactive 262 "Defines how server-exited events must be handled." 263 :group 'lsp-mode 264 :type '(choice (const interactive) 265 (const auto-restart) 266 (const ignore))) 267 268 (defcustom lsp-session-file (expand-file-name (locate-user-emacs-file ".lsp-session-v1")) 269 "File where session information is stored." 270 :group 'lsp-mode 271 :type 'file) 272 273 (defcustom lsp-auto-configure t 274 "Auto configure `lsp-mode' main features. 275 When set to t `lsp-mode' will auto-configure completion, 276 code-actions, breadcrumb, `flycheck', `flymake', `imenu', symbol highlighting, 277 lenses, links, and so on. 278 279 For finer granularity you may use `lsp-enable-*' properties." 280 :group 'lsp-mode 281 :type 'boolean 282 :package-version '(lsp-mode . "6.1")) 283 284 (defcustom lsp-disabled-clients nil 285 "A list of disabled/blocklisted clients. 286 Each entry in the list can be either: 287 a symbol, the server-id for the LSP client, or 288 a cons pair (MAJOR-MODE . CLIENTS), where MAJOR-MODE is the major-mode, 289 and CLIENTS is either a client or a list of clients. 290 291 This option can also be used as a file- or directory-local variable to 292 disable a language server for individual files or directories/projects 293 respectively." 294 :group 'lsp-mode 295 :type '(repeat (symbol)) 296 :safe 'listp 297 :package-version '(lsp-mode . "6.1")) 298 299 (defvar lsp-clients (make-hash-table :test 'eql) 300 "Hash table server-id -> client. 301 It contains all of the clients that are currently registered.") 302 303 (defvar lsp-enabled-clients nil 304 "List of clients allowed to be used for projects. 305 When nil, all registered clients are considered candidates.") 306 307 (defvar lsp-last-id 0 308 "Last request id.") 309 310 (defcustom lsp-before-initialize-hook nil 311 "List of functions to be called before a Language Server has been initialized 312 for a new workspace." 313 :type 'hook 314 :group 'lsp-mode) 315 316 (defcustom lsp-after-initialize-hook nil 317 "List of functions to be called after a Language Server has been initialized 318 for a new workspace." 319 :type 'hook 320 :group 'lsp-mode) 321 322 (defcustom lsp-before-open-hook nil 323 "List of functions to be called before a new file with LSP support is opened." 324 :type 'hook 325 :group 'lsp-mode) 326 327 (defcustom lsp-after-open-hook nil 328 "List of functions to be called after a new file with LSP support is opened." 329 :type 'hook 330 :group 'lsp-mode) 331 332 (defcustom lsp-enable-file-watchers t 333 "If non-nil lsp-mode will watch the files in the workspace if 334 the server has requested that." 335 :type 'boolean 336 :group 'lsp-mode 337 :package-version '(lsp-mode . "6.1")) 338 ;;;###autoload(put 'lsp-enable-file-watchers 'safe-local-variable #'booleanp) 339 340 (define-obsolete-variable-alias 'lsp-file-watch-ignored 'lsp-file-watch-ignored-directories "8.0.0") 341 342 (defcustom lsp-file-watch-ignored-directories 343 '(; SCM tools 344 "[/\\\\]\\.git\\'" 345 "[/\\\\]\\.github\\'" 346 "[/\\\\]\\.gitlab\\'" 347 "[/\\\\]\\.circleci\\'" 348 "[/\\\\]\\.hg\\'" 349 "[/\\\\]\\.bzr\\'" 350 "[/\\\\]_darcs\\'" 351 "[/\\\\]\\.svn\\'" 352 "[/\\\\]_FOSSIL_\\'" 353 ;; IDE or build tools 354 "[/\\\\]\\.idea\\'" 355 "[/\\\\]\\.ensime_cache\\'" 356 "[/\\\\]\\.eunit\\'" 357 "[/\\\\]node_modules" 358 "[/\\\\]\\.yarn\\'" 359 "[/\\\\]\\.fslckout\\'" 360 "[/\\\\]\\.tox\\'" 361 "[/\\\\]\\.nox\\'" 362 "[/\\\\]dist\\'" 363 "[/\\\\]dist-newstyle\\'" 364 "[/\\\\]\\.stack-work\\'" 365 "[/\\\\]\\.bloop\\'" 366 "[/\\\\]\\.metals\\'" 367 "[/\\\\]target\\'" 368 "[/\\\\]\\.ccls-cache\\'" 369 "[/\\\\]\\.vs\\'" 370 "[/\\\\]\\.vscode\\'" 371 "[/\\\\]\\.venv\\'" 372 "[/\\\\]\\.mypy_cache\\'" 373 "[/\\\\]\\.pytest_cache\\'" 374 ;; Swift Package Manager 375 "[/\\\\]\\.build\\'" 376 ;; Python 377 "[/\\\\]__pycache__\\'" 378 ;; Autotools output 379 "[/\\\\]\\.deps\\'" 380 "[/\\\\]build-aux\\'" 381 "[/\\\\]autom4te.cache\\'" 382 "[/\\\\]\\.reference\\'" 383 ;; Bazel 384 "[/\\\\]bazel-[^/\\\\]+\\'" 385 ;; CSharp 386 "[/\\\\]\\.cache[/\\\\]lsp-csharp\\'" 387 "[/\\\\]\\.meta\\'" 388 "[/\\\\]\\.nuget\\'" 389 ;; Unity 390 "[/\\\\]Library\\'" 391 ;; Clojure 392 "[/\\\\]\\.lsp\\'" 393 "[/\\\\]\\.clj-kondo\\'" 394 "[/\\\\]\\.shadow-cljs\\'" 395 "[/\\\\]\\.babel_cache\\'" 396 "[/\\\\]\\.cpcache\\'" 397 "[/\\\\]\\checkouts\\'" 398 ;; Gradle 399 "[/\\\\]\\.gradle\\'" 400 ;; Maven 401 "[/\\\\]\\.m2\\'" 402 ;; .Net Core build-output 403 "[/\\\\]bin/Debug\\'" 404 "[/\\\\]obj\\'" 405 ;; OCaml and Dune 406 "[/\\\\]_opam\\'" 407 "[/\\\\]_build\\'" 408 ;; Elixir 409 "[/\\\\]\\.elixir_ls\\'" 410 ;; Elixir Credo 411 "[/\\\\]\\.elixir-tools\\'" 412 ;; terraform and terragrunt 413 "[/\\\\]\\.terraform\\'" 414 "[/\\\\]\\.terragrunt-cache\\'" 415 ;; nix-direnv 416 "[/\\\\]\\result" 417 "[/\\\\]\\result-bin" 418 "[/\\\\]\\.direnv\\'") 419 "List of regexps matching directory paths which won't be monitored when 420 creating file watches. Customization of this variable is only honored at 421 the global level or at a root of an lsp workspace." 422 :group 'lsp-mode 423 :type '(repeat string) 424 :package-version '(lsp-mode . "8.0.0")) 425 426 (define-obsolete-function-alias 'lsp-file-watch-ignored 'lsp-file-watch-ignored-directories "7.0.1") 427 428 (defun lsp-file-watch-ignored-directories () 429 lsp-file-watch-ignored-directories) 430 431 ;; Allow lsp-file-watch-ignored-directories as a file or directory-local variable 432 ;;;###autoload(put 'lsp-file-watch-ignored-directories 'safe-local-variable 'lsp--string-listp) 433 434 (defcustom lsp-file-watch-ignored-files 435 '( 436 ;; Flycheck tempfiles 437 "[/\\\\]flycheck_[^/\\\\]+\\'" 438 ;; lockfiles 439 "[/\\\\]\\.#[^/\\\\]+\\'" 440 ;; backup files 441 "[/\\\\][^/\\\\]+~\\'" ) 442 "List of regexps matching files for which change events will 443 not be sent to the server. 444 445 This setting has no impact on whether a file-watch is created for 446 a directory; it merely prevents notifications pertaining to 447 matched files from being sent to the server. To prevent a 448 file-watch from being created for a directory, customize 449 `lsp-file-watch-ignored-directories' 450 451 Customization of this variable is only honored at the global 452 level or at a root of an lsp workspace." 453 :group 'lsp-mode 454 :type '(repeat string) 455 :package-version '(lsp-mode . "8.0.0")) 456 457 ;; Allow lsp-file-watch-ignored-files as a file or directory-local variable 458 ;;;###autoload(put 'lsp-file-watch-ignored-files 'safe-local-variable 'lsp--string-listp) 459 460 (defcustom lsp-after-uninitialized-functions nil 461 "List of functions to be called after a Language Server has been uninitialized." 462 :type 'hook 463 :group 'lsp-mode 464 :package-version '(lsp-mode . "6.3")) 465 466 (defconst lsp--sync-full 1) 467 (defconst lsp--sync-incremental 2) 468 469 (defcustom lsp-debounce-full-sync-notifications t 470 "If non-nil debounce full sync events. 471 This flag affects only servers which do not support incremental updates." 472 :type 'boolean 473 :group 'lsp-mode 474 :package-version '(lsp-mode . "6.1")) 475 476 (defcustom lsp-debounce-full-sync-notifications-interval 1.0 477 "Time to wait before sending full sync synchronization after buffer modification." 478 :type 'float 479 :group 'lsp-mode 480 :package-version '(lsp-mode . "6.1")) 481 482 (defvar lsp--stderr-index 0) 483 484 (defvar lsp--delayed-requests nil) 485 (defvar lsp--delay-timer nil) 486 487 (defcustom lsp-document-sync-method nil 488 "How to sync the document with the language server." 489 :type '(choice (const :tag "Documents are synced by always sending the full content of the document." lsp--sync-full) 490 (const :tag "Documents are synced by always sending incremental changes to the document." lsp--sync-incremental) 491 (const :tag "Use the method recommended by the language server." nil)) 492 :group 'lsp-mode) 493 494 (defcustom lsp-auto-execute-action t 495 "Auto-execute single action." 496 :type 'boolean 497 :group 'lsp-mode) 498 499 (defcustom lsp-enable-links t 500 "If non-nil, all references to links in a file will be made clickable, if 501 supported by the language server." 502 :type 'boolean 503 :group 'lsp-mode 504 :package-version '(lsp-mode . "6.1")) 505 506 (defcustom lsp-enable-imenu t 507 "If non-nil, automatically enable `imenu' integration when server provides 508 `textDocument/documentSymbol'." 509 :type 'boolean 510 :group 'lsp-mode 511 :package-version '(lsp-mode . "6.2")) 512 513 (defcustom lsp-enable-dap-auto-configure t 514 "If non-nil, enable `dap-auto-configure-mode`." 515 :type 'boolean 516 :group 'lsp-mode 517 :package-version '(lsp-mode . "7.0")) 518 519 (defcustom lsp-eldoc-enable-hover t 520 "If non-nil, `eldoc' will display hover info when it is present." 521 :type 'boolean 522 :group 'lsp-mode) 523 524 (defcustom lsp-eldoc-render-all nil 525 "Display all of the info returned by document/onHover. 526 If this is set to nil, `eldoc' will show only the symbol information." 527 :type 'boolean 528 :group 'lsp-mode) 529 530 (define-obsolete-variable-alias 'lsp-enable-completion-at-point 531 'lsp-completion-enable "lsp-mode 7.0.1") 532 533 (defcustom lsp-completion-enable t 534 "Enable `completion-at-point' integration." 535 :type 'boolean 536 :group 'lsp-completion) 537 538 (defcustom lsp-enable-symbol-highlighting t 539 "Highlight references of the symbol at point." 540 :type 'boolean 541 :group 'lsp-mode) 542 543 (defcustom lsp-enable-xref t 544 "Enable xref integration." 545 :type 'boolean 546 :group 'lsp-mode) 547 548 (defcustom lsp-references-exclude-definition nil 549 "If non-nil, exclude declarations when finding references." 550 :type 'boolean 551 :group 'lsp-mode) 552 553 (defcustom lsp-enable-indentation t 554 "Indent regions using the file formatting functionality provided by the 555 language server." 556 :type 'boolean 557 :group 'lsp-mode) 558 559 (defcustom lsp-enable-on-type-formatting t 560 "Enable `textDocument/onTypeFormatting' integration." 561 :type 'boolean 562 :group 'lsp-mode) 563 564 (defcustom lsp-enable-text-document-color t 565 "Enable `textDocument/documentColor' integration." 566 :type 'boolean 567 :group 'lsp-mode) 568 569 (defcustom lsp-before-save-edits t 570 "If non-nil, `lsp-mode' will apply edits suggested by the language server 571 before saving a document." 572 :type 'boolean 573 :group 'lsp-mode) 574 575 (defcustom lsp-after-apply-edits-hook nil 576 "Hooks to run when text edit is applied. 577 It contains the operation source." 578 :type 'hook 579 :group 'lsp-mode 580 :package-version '(lsp-mode . "8.0.0")) 581 582 (defcustom lsp-apply-edits-after-file-operations t 583 "Whether to apply edits returned by server after file operations if any. 584 Applicable only if server supports workspace.fileOperations for operations: 585 `workspace/willRenameFiles', `workspace/willCreateFiles' and 586 `workspace/willDeleteFiles'." 587 :group 'lsp-mode 588 :type 'boolean) 589 590 (defcustom lsp-modeline-code-actions-enable t 591 "Whether to show code actions on modeline." 592 :type 'boolean 593 :group 'lsp-modeline) 594 595 (defcustom lsp-modeline-diagnostics-enable t 596 "Whether to show diagnostics on modeline." 597 :type 'boolean 598 :group 'lsp-modeline) 599 600 (defcustom lsp-modeline-workspace-status-enable t 601 "Whether to show workspace status on modeline." 602 :type 'boolean 603 :group 'lsp-modeline 604 :package-version '(lsp-mode . "8.0.0")) 605 606 (defcustom lsp-headerline-breadcrumb-enable t 607 "Whether to enable breadcrumb on headerline." 608 :type 'boolean 609 :group 'lsp-headerline) 610 611 (defcustom lsp-configure-hook nil 612 "Hooks to run when `lsp-configure-buffer' is called." 613 :type 'hook 614 :group 'lsp-mode) 615 616 (defcustom lsp-unconfigure-hook nil 617 "Hooks to run when `lsp-unconfig-buffer' is called." 618 :type 'hook 619 :group 'lsp-mode) 620 621 (defcustom lsp-after-diagnostics-hook nil 622 "Hooks to run after diagnostics are received. 623 Note: it runs only if the receiving buffer is open. Use 624 `lsp-diagnostics-updated-hook'if you want to be notified when 625 diagnostics have changed." 626 :type 'hook 627 :group 'lsp-mode) 628 629 (define-obsolete-variable-alias 'lsp-after-diagnostics-hook 630 'lsp-diagnostics-updated-hook "lsp-mode 6.4") 631 632 (defcustom lsp-diagnostics-updated-hook nil 633 "Hooks to run after diagnostics are received." 634 :type 'hook 635 :group 'lsp-mode) 636 637 (define-obsolete-variable-alias 'lsp-workspace-folders-changed-hook 638 'lsp-workspace-folders-changed-functions "lsp-mode 6.3") 639 640 (defcustom lsp-workspace-folders-changed-functions nil 641 "Hooks to run after the folders has changed. 642 The hook will receive two parameters list of added and removed folders." 643 :type 'hook 644 :group 'lsp-mode) 645 646 (define-obsolete-variable-alias 'lsp-eldoc-hook 'eldoc-documentation-functions "lsp-mode 9.0.0") 647 648 (defcustom lsp-before-apply-edits-hook nil 649 "Hooks to run before applying edits." 650 :type 'hook 651 :group 'lsp-mode) 652 653 (defgroup lsp-imenu nil 654 "LSP Imenu." 655 :group 'lsp-mode 656 :tag "LSP Imenu") 657 658 (defcustom lsp-imenu-show-container-name t 659 "Display the symbol's container name in an imenu entry." 660 :type 'boolean 661 :group 'lsp-imenu) 662 663 (defcustom lsp-imenu-container-name-separator "/" 664 "Separator string to use to separate the container name from the symbol while 665 displaying imenu entries." 666 :type 'string 667 :group 'lsp-imenu) 668 669 (defcustom lsp-imenu-sort-methods '(kind name) 670 "How to sort the imenu items. 671 672 The value is a list of `kind' `name' or `position'. Priorities 673 are determined by the index of the element." 674 :type '(repeat (choice (const name) 675 (const position) 676 (const kind))) 677 :group 'lsp-imenu) 678 679 (defcustom lsp-imenu-index-symbol-kinds nil 680 "Which symbol kinds to show in imenu." 681 :type '(repeat (choice (const :tag "Miscellaneous" nil) 682 (const :tag "File" File) 683 (const :tag "Module" Module) 684 (const :tag "Namespace" Namespace) 685 (const :tag "Package" Package) 686 (const :tag "Class" Class) 687 (const :tag "Method" Method) 688 (const :tag "Property" Property) 689 (const :tag "Field" Field) 690 (const :tag "Constructor" Constructor) 691 (const :tag "Enum" Enum) 692 (const :tag "Interface" Interface) 693 (const :tag "Function" Function) 694 (const :tag "Variable" Variable) 695 (const :tag "Constant" Constant) 696 (const :tag "String" String) 697 (const :tag "Number" Number) 698 (const :tag "Boolean" Boolean) 699 (const :tag "Array" Array) 700 (const :tag "Object" Object) 701 (const :tag "Key" Key) 702 (const :tag "Null" Null) 703 (const :tag "Enum Member" EnumMember) 704 (const :tag "Struct" Struct) 705 (const :tag "Event" Event) 706 (const :tag "Operator" Operator) 707 (const :tag "Type Parameter" TypeParameter))) 708 :group 'lsp-imenu) 709 710 ;; vibhavp: Should we use a lower value (5)? 711 (defcustom lsp-response-timeout 10 712 "Number of seconds to wait for a response from the language server before 713 timing out. Nil if no timeout." 714 :type '(choice 715 (number :tag "Seconds") 716 (const :tag "No timeout" nil)) 717 :group 'lsp-mode) 718 719 (defcustom lsp-tcp-connection-timeout 2 720 "The timeout for tcp connection in seconds." 721 :type 'number 722 :group 'lsp-mode 723 :package-version '(lsp-mode . "6.2")) 724 725 (defconst lsp--imenu-compare-function-alist 726 (list (cons 'name #'lsp--imenu-compare-name) 727 (cons 'kind #'lsp--imenu-compare-kind) 728 (cons 'position #'lsp--imenu-compare-line-col)) 729 "An alist of (METHOD . FUNCTION). 730 METHOD is one of the symbols accepted by 731 `lsp-imenu-sort-methods'. 732 733 FUNCTION takes two hash tables representing DocumentSymbol. It 734 returns a negative number, 0, or a positive number indicating 735 whether the first parameter is less than, equal to, or greater 736 than the second parameter.") 737 738 (defcustom lsp-diagnostic-clean-after-change nil 739 "When non-nil, clean the diagnostics on change. 740 741 Note that when that setting is nil, `lsp-mode' will show stale 742 diagnostics until server publishes the new set of diagnostics" 743 :type 'boolean 744 :group 'lsp-diagnostics 745 :package-version '(lsp-mode . "7.0.1")) 746 747 (defcustom lsp-server-trace nil 748 "Request tracing on the server side. 749 The actual trace output at each level depends on the language server in use. 750 Changes take effect only when a new session is started." 751 :type '(choice (const :tag "Disabled" "off") 752 (const :tag "Messages only" "messages") 753 (const :tag "Verbose" "verbose") 754 (const :tag "Default (disabled)" nil)) 755 :group 'lsp-mode 756 :package-version '(lsp-mode . "6.1")) 757 758 (defcustom lsp-auto-touch-files t 759 "If non-nil ensure the files exist before sending 760 `textDocument/didOpen' notification." 761 :type 'boolean 762 :group 'lsp-mode 763 :package-version '(lsp-mode . "9.0.0")) 764 765 (defvar lsp-language-id-configuration 766 '(("\\(^CMakeLists\\.txt\\|\\.cmake\\)\\'" . "cmake") 767 ("\\(^Dockerfile\\(?:\\..*\\)?\\|\\.[Dd]ockerfile\\)\\'" . "dockerfile") 768 ("\\.astro$" . "astro") 769 ("\\.cs\\'" . "csharp") 770 ("\\.css$" . "css") 771 ("\\.cypher$" . "cypher") 772 ("\\.ebuild$" . "shellscript") 773 ("\\.go\\'" . "go") 774 ("\\.html$" . "html") 775 ("\\.hx$" . "haxe") 776 ("\\.hy$" . "hy") 777 ("\\.java\\'" . "java") 778 ("\\.jq$" . "jq") 779 ("\\.js$" . "javascript") 780 ("\\.json$" . "json") 781 ("\\.jsonc$" . "jsonc") 782 ("\\.jsonnet$" . "jsonnet") 783 ("\\.jsx$" . "javascriptreact") 784 ("\\.lua$" . "lua") 785 ("\\.mdx\\'" . "mdx") 786 ("\\.nu$" . "nushell") 787 ("\\.php$" . "php") 788 ("\\.ps[dm]?1\\'" . "powershell") 789 ("\\.rs\\'" . "rust") 790 ("\\.spec\\'" . "rpm-spec") 791 ("\\.sql$" . "sql") 792 ("\\.svelte$" . "svelte") 793 ("\\.toml\\'" . "toml") 794 ("\\.ts$" . "typescript") 795 ("\\.tsx$" . "typescriptreact") 796 ("\\.ttcn3$" . "ttcn3") 797 ("\\.vue$" . "vue") 798 ("\\.xml$" . "xml") 799 ("\\ya?ml$" . "yaml") 800 ("^PKGBUILD$" . "shellscript") 801 ("^go\\.mod\\'" . "go.mod") 802 ("^settings\\.json$" . "jsonc") 803 ("^yang\\.settings$" . "jsonc") 804 ("^meson\\(_options\\.txt\\|\\.\\(build\\|format\\)\\)\\'" . "meson") 805 (ada-mode . "ada") 806 (ada-ts-mode . "ada") 807 (gpr-mode . "gpr") 808 (gpr-ts-mode . "gpr") 809 (awk-mode . "awk") 810 (awk-ts-mode . "awk") 811 (nxml-mode . "xml") 812 (sql-mode . "sql") 813 (vimrc-mode . "vim") 814 (vimscript-ts-mode . "vim") 815 (sh-mode . "shellscript") 816 (bash-ts-mode . "shellscript") 817 (ebuild-mode . "shellscript") 818 (pkgbuild-mode . "shellscript") 819 (envrc-file-mode . "shellscript") 820 (scala-mode . "scala") 821 (scala-ts-mode . "scala") 822 (julia-mode . "julia") 823 (julia-ts-mode . "julia") 824 (clojure-mode . "clojure") 825 (clojurec-mode . "clojure") 826 (clojurescript-mode . "clojurescript") 827 (clojure-ts-mode . "clojure") 828 (clojure-ts-clojurec-mode . "clojure") 829 (clojure-ts-clojurescript-mode . "clojurescript") 830 (java-mode . "java") 831 (java-ts-mode . "java") 832 (jdee-mode . "java") 833 (groovy-mode . "groovy") 834 (python-mode . "python") 835 (python-ts-mode . "python") 836 (cython-mode . "python") 837 ("\\(\\.mojo\\|\\.🔥\\)\\'" . "mojo") 838 (lsp--render-markdown . "markdown") 839 (move-mode . "move") 840 (rust-mode . "rust") 841 (rust-ts-mode . "rust") 842 (rustic-mode . "rust") 843 (kotlin-mode . "kotlin") 844 (kotlin-ts-mode . "kotlin") 845 (css-mode . "css") 846 (css-ts-mode . "css") 847 (less-mode . "less") 848 (less-css-mode . "less") 849 (lua-mode . "lua") 850 (lua-ts-mode . "lua") 851 (sass-mode . "sass") 852 (ssass-mode . "sass") 853 (scss-mode . "scss") 854 (scad-mode . "openscad") 855 (xml-mode . "xml") 856 (c-mode . "c") 857 (c-ts-mode . "c") 858 (c++-mode . "cpp") 859 (c++-ts-mode . "cpp") 860 (cuda-mode . "cuda") 861 (objc-mode . "objective-c") 862 (html-mode . "html") 863 (html-ts-mode . "html") 864 (sgml-mode . "html") 865 (mhtml-mode . "html") 866 (mint-mode . "mint") 867 (go-dot-mod-mode . "go.mod") 868 (go-mod-ts-mode . "go.mod") 869 (go-mode . "go") 870 (go-ts-mode . "go") 871 (graphql-mode . "graphql") 872 (haskell-mode . "haskell") 873 (hack-mode . "hack") 874 (php-mode . "php") 875 (php-ts-mode . "php") 876 (powershell-mode . "powershell") 877 (powershell-mode . "PowerShell") 878 (powershell-ts-mode . "powershell") 879 (json-mode . "json") 880 (json-ts-mode . "json") 881 (jsonc-mode . "jsonc") 882 (rjsx-mode . "javascript") 883 (js2-mode . "javascript") 884 (js-mode . "javascript") 885 (js-ts-mode . "javascript") 886 (typescript-mode . "typescript") 887 (typescript-ts-mode . "typescript") 888 (tsx-ts-mode . "typescriptreact") 889 (svelte-mode . "svelte") 890 (fsharp-mode . "fsharp") 891 (reason-mode . "reason") 892 (caml-mode . "ocaml") 893 (tuareg-mode . "ocaml") 894 (swift-mode . "swift") 895 (elixir-mode . "elixir") 896 (elixir-ts-mode . "elixir") 897 (heex-ts-mode . "elixir") 898 (conf-javaprop-mode . "spring-boot-properties") 899 (yaml-mode . "yaml") 900 (yaml-ts-mode . "yaml") 901 (ruby-mode . "ruby") 902 (enh-ruby-mode . "ruby") 903 (ruby-ts-mode . "ruby") 904 (fortran-mode . "fortran") 905 (f90-mode . "fortran") 906 (elm-mode . "elm") 907 (dart-mode . "dart") 908 (erlang-mode . "erlang") 909 (dockerfile-mode . "dockerfile") 910 (dockerfile-ts-mode . "dockerfile") 911 (csharp-mode . "csharp") 912 (csharp-tree-sitter-mode . "csharp") 913 (csharp-ts-mode . "csharp") 914 (plain-tex-mode . "plaintex") 915 (context-mode . "context") 916 (cypher-mode . "cypher") 917 (latex-mode . "latex") 918 (v-mode . "v") 919 (vhdl-mode . "vhdl") 920 (vhdl-ts-mode . "vhdl") 921 (verilog-mode . "verilog") 922 (terraform-mode . "terraform") 923 (ess-julia-mode . "julia") 924 (ess-r-mode . "r") 925 (crystal-mode . "crystal") 926 (nim-mode . "nim") 927 (dhall-mode . "dhall") 928 (cmake-mode . "cmake") 929 (cmake-ts-mode . "cmake") 930 (purescript-mode . "purescript") 931 (gdscript-mode . "gdscript") 932 (gdscript-ts-mode . "gdscript") 933 (perl-mode . "perl") 934 (cperl-mode . "perl") 935 (robot-mode . "robot") 936 (racket-mode . "racket") 937 (nix-mode . "nix") 938 (nix-ts-mode . "Nix") 939 (prolog-mode . "prolog") 940 (vala-mode . "vala") 941 (actionscript-mode . "actionscript") 942 (d-mode . "d") 943 (zig-mode . "zig") 944 (text-mode . "plaintext") 945 (markdown-mode . "markdown") 946 (gfm-mode . "markdown") 947 (beancount-mode . "beancount") 948 (conf-toml-mode . "toml") 949 (toml-ts-mode . "toml") 950 (org-mode . "org") 951 (org-journal-mode . "org") 952 (nginx-mode . "nginx") 953 (magik-mode . "magik") 954 (magik-ts-mode . "magik") 955 (idris-mode . "idris") 956 (idris2-mode . "idris2") 957 (gleam-mode . "gleam") 958 (graphviz-dot-mode . "dot") 959 (tiltfile-mode . "tiltfile") 960 (solidity-mode . "solidity") 961 (bibtex-mode . "bibtex") 962 (rst-mode . "restructuredtext") 963 (glsl-mode . "glsl") 964 (shader-mode . "shaderlab") 965 (wgsl-mode . "wgsl") 966 (jq-mode . "jq") 967 (jq-ts-mode . "jq") 968 (protobuf-mode . "protobuf") 969 (nushell-mode . "nushell") 970 (nushell-ts-mode . "nushell") 971 (meson-mode . "meson") 972 (yang-mode . "yang")) 973 "Language id configuration.") 974 975 (defvar lsp--last-active-workspaces nil 976 "Keep track of last active workspace. 977 We want to try the last workspace first when jumping into a library 978 directory") 979 980 (defvar lsp-method-requirements 981 '(("textDocument/callHierarchy" :capability :callHierarchyProvider) 982 ("textDocument/codeAction" :capability :codeActionProvider) 983 ("codeAction/resolve" 984 :check-command (lambda (workspace) 985 (with-lsp-workspace workspace 986 (lsp:code-action-options-resolve-provider? 987 (lsp--capability-for-method "textDocument/codeAction"))))) 988 ("textDocument/codeLens" :capability :codeLensProvider) 989 ("textDocument/completion" :capability :completionProvider) 990 ("completionItem/resolve" 991 :check-command (lambda (wk) 992 (with-lsp-workspace wk 993 (lsp:completion-options-resolve-provider? 994 (lsp--capability-for-method "textDocument/completion"))))) 995 ("textDocument/declaration" :capability :declarationProvider) 996 ("textDocument/definition" :capability :definitionProvider) 997 ("textDocument/documentColor" :capability :colorProvider) 998 ("textDocument/documentLink" :capability :documentLinkProvider) 999 ("textDocument/inlayHint" :capability :inlayHintProvider) 1000 ("textDocument/documentHighlight" :capability :documentHighlightProvider) 1001 ("textDocument/documentSymbol" :capability :documentSymbolProvider) 1002 ("textDocument/foldingRange" :capability :foldingRangeProvider) 1003 ("textDocument/formatting" :capability :documentFormattingProvider) 1004 ("textDocument/hover" :capability :hoverProvider) 1005 ("textDocument/implementation" :capability :implementationProvider) 1006 ("textDocument/linkedEditingRange" :capability :linkedEditingRangeProvider) 1007 ("textDocument/onTypeFormatting" :capability :documentOnTypeFormattingProvider) 1008 ("textDocument/prepareRename" 1009 :check-command (lambda (workspace) 1010 (with-lsp-workspace workspace 1011 (lsp:rename-options-prepare-provider? 1012 (lsp--capability-for-method "textDocument/rename"))))) 1013 ("textDocument/rangeFormatting" :capability :documentRangeFormattingProvider) 1014 ("textDocument/references" :capability :referencesProvider) 1015 ("textDocument/rename" :capability :renameProvider) 1016 ("textDocument/selectionRange" :capability :selectionRangeProvider) 1017 ("textDocument/semanticTokens" :capability :semanticTokensProvider) 1018 ("textDocument/semanticTokensFull" 1019 :check-command (lambda (workspace) 1020 (with-lsp-workspace workspace 1021 (lsp-get (lsp--capability :semanticTokensProvider) :full)))) 1022 ("textDocument/semanticTokensFull/Delta" 1023 :check-command (lambda (workspace) 1024 (with-lsp-workspace workspace 1025 (let ((capFull (lsp-get (lsp--capability :semanticTokensProvider) :full))) 1026 (and (not (booleanp capFull)) (lsp-get capFull :delta)))))) 1027 ("textDocument/semanticTokensRangeProvider" 1028 :check-command (lambda (workspace) 1029 (with-lsp-workspace workspace 1030 (lsp-get (lsp--capability :semanticTokensProvider) :range)))) 1031 ("textDocument/signatureHelp" :capability :signatureHelpProvider) 1032 ("textDocument/typeDefinition" :capability :typeDefinitionProvider) 1033 ("textDocument/typeHierarchy" :capability :typeHierarchyProvider) 1034 ("workspace/executeCommand" :capability :executeCommandProvider) 1035 ("workspace/symbol" :capability :workspaceSymbolProvider)) 1036 1037 "Map methods to requirements. 1038 It is used by request-sending functions to determine which server 1039 must be used for handling a particular message.") 1040 1041 (defconst lsp--file-change-type 1042 `((created . 1) 1043 (changed . 2) 1044 (deleted . 3))) 1045 1046 (defconst lsp--watch-kind 1047 `((create . 1) 1048 (change . 2) 1049 (delete . 4))) 1050 1051 (defvar lsp-window-body-width 40 1052 "Window body width when rendering doc.") 1053 1054 (defface lsp-face-highlight-textual 1055 '((t :inherit highlight)) 1056 "Face used for textual occurrences of symbols." 1057 :group 'lsp-mode) 1058 1059 (defface lsp-face-highlight-read 1060 '((t :inherit highlight :underline t)) 1061 "Face used for highlighting symbols being read." 1062 :group 'lsp-mode) 1063 1064 (defface lsp-face-highlight-write 1065 '((t :inherit highlight :weight bold)) 1066 "Face used for highlighting symbols being written to." 1067 :group 'lsp-mode) 1068 1069 (define-obsolete-variable-alias 'lsp-lens-auto-enable 1070 'lsp-lens-enable "lsp-mode 7.0.1") 1071 1072 (defcustom lsp-lens-enable t 1073 "Auto enable lenses if server supports." 1074 :group 'lsp-lens 1075 :type 'boolean 1076 :package-version '(lsp-mode . "6.3")) 1077 1078 (defcustom lsp-symbol-highlighting-skip-current nil 1079 "If non-nil skip current symbol when setting symbol highlights." 1080 :group 'lsp-mode 1081 :type 'boolean) 1082 1083 (defcustom lsp-file-watch-threshold 1000 1084 "Show warning if the files to watch are more than. 1085 Set to nil to disable the warning." 1086 :type 'number 1087 :group 'lsp-mode) 1088 ;;;###autoload(put 'lsp-file-watch-threshold 'safe-local-variable (lambda (i) (or (numberp i) (not i)))) 1089 1090 (defvar lsp-custom-markup-modes 1091 '((rust-mode "no_run" "rust,no_run" "rust,ignore" "rust,should_panic")) 1092 "Mode to uses with markdown code blocks. 1093 They are added to `markdown-code-lang-modes'") 1094 1095 (defcustom lsp-signature-render-documentation t 1096 "Display signature documentation in `eldoc'." 1097 :type 'boolean 1098 :group 'lsp-mode 1099 :package-version '(lsp-mode . "6.2")) 1100 1101 (defcustom lsp-signature-auto-activate '(:on-trigger-char :on-server-request) 1102 "Auto activate signature conditions." 1103 :type '(repeat (choice (const :tag "On trigger chars pressed." :on-trigger-char) 1104 (const :tag "After selected completion." :after-completion) 1105 (const :tag "When the server has sent show signature help." :on-server-request))) 1106 :group 'lsp-mode 1107 :package-version '(lsp-mode . "6.2")) 1108 1109 (defcustom lsp-signature-doc-lines 20 1110 "If number, limit the number of lines to show in the docs." 1111 :type 'number 1112 :group 'lsp-mode 1113 :package-version '(lsp-mode . "6.3")) 1114 1115 (defcustom lsp-signature-function 'lsp-lv-message 1116 "The function used for displaying signature info. 1117 It will be called with one param - the signature info. When 1118 called with nil the signature info must be cleared." 1119 :type 'function 1120 :group 'lsp-mode 1121 :package-version '(lsp-mode . "6.3")) 1122 1123 (defcustom lsp-keymap-prefix "s-l" 1124 "LSP-mode keymap prefix." 1125 :group 'lsp-mode 1126 :type 'string 1127 :package-version '(lsp-mode . "6.3")) 1128 1129 (defvar-local lsp--buffer-workspaces () 1130 "List of the buffer workspaces.") 1131 1132 (defvar-local lsp--buffer-deferred nil 1133 "Whether buffer was loaded via `lsp-deferred'.") 1134 1135 (defvar lsp--session nil 1136 "Contain the `lsp-session' for the current Emacs instance.") 1137 1138 (defvar lsp--tcp-port 10000) 1139 1140 (defvar lsp--client-packages-required nil 1141 "If nil, `lsp-client-packages' are yet to be required.") 1142 1143 (defvar lsp--tcp-server-port 0 1144 "The server socket which is opened when using `lsp-tcp-server' (a server 1145 socket is opened in Emacs and the language server connects to it). The 1146 default value of 0 ensures that a random high port is used. Set it to a positive 1147 integer to use a specific port.") 1148 1149 (defvar lsp--tcp-server-wait-seconds 10 1150 "Wait this amount of time for the client to connect to our server socket 1151 when using `lsp-tcp-server'.") 1152 1153 (defvar-local lsp--document-symbols nil 1154 "The latest document symbols.") 1155 1156 (defvar-local lsp--document-selection-range-cache nil 1157 "The document selection cache.") 1158 1159 (defvar-local lsp--document-symbols-request-async nil 1160 "If non-nil, request document symbols asynchronously.") 1161 1162 (defvar-local lsp--document-symbols-tick -1 1163 "The value of `buffer-chars-modified-tick' when document 1164 symbols were last retrieved.") 1165 1166 (defvar-local lsp--have-document-highlights nil 1167 "Set to `t' on symbol highlighting, cleared on 1168 `lsp--cleanup-highlights-if-needed'. Checking a separately 1169 defined flag is substantially faster than unconditionally 1170 calling `remove-overlays'.") 1171 1172 ;; Buffer local variable for storing number of lines. 1173 (defvar lsp--log-lines) 1174 1175 (defvar-local lsp--eldoc-saved-message nil) 1176 1177 (defvar lsp--on-change-timer nil) 1178 (defvar lsp--on-idle-timer nil) 1179 1180 (defvar-local lsp--signature-last nil) 1181 (defvar-local lsp--signature-last-index nil) 1182 (defvar lsp--signature-last-buffer nil) 1183 1184 (defvar-local lsp--virtual-buffer-point-max nil) 1185 1186 (cl-defmethod lsp-execute-command (_server _command _arguments) 1187 "Ask SERVER to execute COMMAND with ARGUMENTS.") 1188 1189 (defun lsp-elt (sequence n) 1190 "Return Nth element of SEQUENCE or nil if N is out of range." 1191 (cond 1192 ((listp sequence) (elt sequence n)) 1193 ((arrayp sequence) 1194 (and (> (length sequence) n) (aref sequence n))) 1195 (t (and (> (length sequence) n) (elt sequence n))))) 1196 1197 ;; define seq-first and seq-rest for older emacs 1198 (defun lsp-seq-first (sequence) 1199 "Return the first element of SEQUENCE." 1200 (lsp-elt sequence 0)) 1201 1202 (defun lsp-seq-rest (sequence) 1203 "Return a sequence of the elements of SEQUENCE except the first one." 1204 (seq-drop sequence 1)) 1205 1206 ;;;###autoload 1207 (defun lsp--string-listp (sequence) 1208 "Return t if all elements of SEQUENCE are strings, else nil." 1209 (not (seq-find (lambda (x) (not (stringp x))) sequence))) 1210 1211 (defun lsp--string-vector-p (candidate) 1212 "Returns true if CANDIDATE is a vector data structure and 1213 every element of it is of type string, else nil." 1214 (and 1215 (vectorp candidate) 1216 (seq-every-p #'stringp candidate))) 1217 1218 (make-obsolete 'lsp--string-vector-p nil "lsp-mode 8.0.0") 1219 1220 (defun lsp--editable-vector-match (widget value) 1221 "Function for `lsp-editable-vector' :match." 1222 ;; Value must be a list or a vector and all the members must match the type. 1223 (and (or (listp value) (vectorp value)) 1224 (length (cdr (lsp--editable-vector-match-inline widget value))))) 1225 1226 (defun lsp--editable-vector-match-inline (widget value) 1227 "Value for `lsp-editable-vector' :match-inline." 1228 (let ((type (nth 0 (widget-get widget :args))) 1229 (ok t) 1230 found) 1231 (while (and value ok) 1232 (let ((answer (widget-match-inline type value))) 1233 (if answer 1234 (let ((head (if (vectorp answer) (aref answer 0) (car answer))) 1235 (tail (if (vectorp answer) (seq-drop 1 answer) (cdr answer)))) 1236 (setq found (append found head) 1237 value tail)) 1238 (setq ok nil)))) 1239 (cons found value))) 1240 1241 (defun lsp--editable-vector-value-to-external (_widget internal-value) 1242 "Convert the internal list value to a vector." 1243 (if (listp internal-value) 1244 (apply 'vector internal-value) 1245 internal-value)) 1246 1247 (defun lsp--editable-vector-value-to-internal (_widget external-value) 1248 "Convert the external vector value to a list." 1249 (if (vectorp external-value) 1250 (append external-value nil) 1251 external-value)) 1252 1253 (define-widget 'lsp--editable-vector 'editable-list 1254 "A subclass of `editable-list' that accepts and returns a 1255 vector instead of a list." 1256 :value-to-external 'lsp--editable-vector-value-to-external 1257 :value-to-internal 'lsp--editable-vector-value-to-internal 1258 :match 'lsp--editable-vector-match 1259 :match-inline 'lsp--editable-vector-match-inline) 1260 1261 (define-widget 'lsp-repeatable-vector 'lsp--editable-vector 1262 "A variable length homogeneous vector." 1263 :tag "Repeat" 1264 :format "%{%t%}:\n%v%i\n") 1265 1266 (define-widget 'lsp-string-vector 'lazy 1267 "A vector of zero or more elements, every element of which is a string. 1268 Appropriate for any language-specific `defcustom' that needs to 1269 serialize as a JSON array of strings. 1270 1271 Deprecated. Use `lsp-repeatable-vector' instead. " 1272 :offset 4 1273 :tag "Vector" 1274 :type '(lsp-repeatable-vector string)) 1275 1276 (make-obsolete 'lsp-string-vector nil "lsp-mode 8.0.0") 1277 1278 (defvar lsp--show-message t 1279 "If non-nil, show debug message from `lsp-mode'.") 1280 1281 (defun lsp--message (format &rest args) 1282 "Wrapper for `message' 1283 1284 We `inhibit-message' the message when the cursor is in the 1285 minibuffer and when emacs version is before emacs 27 due to the 1286 fact that we often use `lsp--info', `lsp--warn' and `lsp--error' 1287 in async context and the call to these function is removing the 1288 minibuffer prompt. The issue with async messages is already fixed 1289 in emacs 27. 1290 1291 See #2049" 1292 (when lsp--show-message 1293 (let ((inhibit-message (or inhibit-message 1294 (and (minibufferp) 1295 (version< emacs-version "27.0"))))) 1296 (apply #'message format args)))) 1297 1298 (defun lsp--info (format &rest args) 1299 "Display lsp info message with FORMAT with ARGS." 1300 (lsp--message "%s :: %s" (propertize "LSP" 'face 'success) (apply #'format format args))) 1301 1302 (defun lsp--warn (format &rest args) 1303 "Display lsp warn message with FORMAT with ARGS." 1304 (lsp--message "%s :: %s" (propertize "LSP" 'face 'warning) (apply #'format format args))) 1305 1306 (defun lsp--error (format &rest args) 1307 "Display lsp error message with FORMAT with ARGS." 1308 (lsp--message "%s :: %s" (propertize "LSP" 'face 'error) (apply #'format format args))) 1309 1310 (defun lsp-log (format &rest args) 1311 "Log message to the ’*lsp-log*’ buffer. 1312 1313 FORMAT and ARGS i the same as for `message'." 1314 (when lsp-log-max 1315 (let ((log-buffer (get-buffer "*lsp-log*")) 1316 (inhibit-read-only t)) 1317 (unless log-buffer 1318 (setq log-buffer (get-buffer-create "*lsp-log*")) 1319 (with-current-buffer log-buffer 1320 (buffer-disable-undo) 1321 (view-mode 1) 1322 (set (make-local-variable 'lsp--log-lines) 0))) 1323 (with-current-buffer log-buffer 1324 (save-excursion 1325 (let* ((message (apply 'format format args)) 1326 ;; Count newlines in message. 1327 (newlines (1+ (cl-loop with start = 0 1328 for count from 0 1329 while (string-match "\n" message start) 1330 do (setq start (match-end 0)) 1331 finally return count)))) 1332 (goto-char (point-max)) 1333 1334 ;; in case the buffer is not empty insert before last \n to preserve 1335 ;; the point position(in case it is in the end) 1336 (if (eq (point) (point-min)) 1337 (progn 1338 (insert "\n") 1339 (backward-char)) 1340 (backward-char) 1341 (insert "\n")) 1342 (insert message) 1343 1344 (setq lsp--log-lines (+ lsp--log-lines newlines)) 1345 1346 (when (and (integerp lsp-log-max) (> lsp--log-lines lsp-log-max)) 1347 (let ((to-delete (- lsp--log-lines lsp-log-max))) 1348 (goto-char (point-min)) 1349 (forward-line to-delete) 1350 (delete-region (point-min) (point)) 1351 (setq lsp--log-lines lsp-log-max))))))))) 1352 1353 (defalias 'lsp-message 'lsp-log) 1354 1355 (defalias 'lsp-ht 'ht) 1356 1357 (defalias 'lsp-file-local-name 'file-local-name) 1358 1359 (defun lsp-f-canonical (file-name) 1360 "Return the canonical FILE-NAME, without a trailing slash." 1361 (directory-file-name (expand-file-name file-name))) 1362 1363 (defalias 'lsp-canonical-file-name 'lsp-f-canonical) 1364 1365 (defun lsp-f-same? (path-a path-b) 1366 "Return t if PATH-A and PATH-B are references to the same file. 1367 Symlinks are not followed." 1368 (when (and (f-exists? path-a) 1369 (f-exists? path-b)) 1370 (equal 1371 (lsp-f-canonical (directory-file-name (f-expand path-a))) 1372 (lsp-f-canonical (directory-file-name (f-expand path-b)))))) 1373 1374 (defun lsp-f-parent (path) 1375 "Return the parent directory to PATH. 1376 Symlinks are not followed." 1377 (let ((parent (file-name-directory 1378 (directory-file-name (f-expand path default-directory))))) 1379 (unless (lsp-f-same? path parent) 1380 (if (f-relative? path) 1381 (f-relative parent) 1382 (directory-file-name parent))))) 1383 1384 (defun lsp-f-ancestor-of? (path-a path-b) 1385 "Return t if PATH-A is an ancestor of PATH-B. 1386 Symlinks are not followed." 1387 (unless (lsp-f-same? path-a path-b) 1388 (s-prefix? (concat (lsp-f-canonical path-a) (f-path-separator)) 1389 (lsp-f-canonical path-b)))) 1390 1391 (defun lsp--merge-results (results method) 1392 "Merge RESULTS by filtering the empty hash-tables and merging 1393 the lists according to METHOD." 1394 (pcase (--map (if (vectorp it) 1395 (append it nil) it) 1396 (-filter #'identity results)) 1397 (`() ()) 1398 ;; only one result - simply return it 1399 (`(,fst) fst) 1400 ;; multiple results merge it based on strategy 1401 (results 1402 (pcase method 1403 ("textDocument/hover" (pcase (seq-filter 1404 (-compose #'not #'lsp-empty?) 1405 results) 1406 (`(,hover) hover) 1407 (hovers (lsp-make-hover 1408 :contents 1409 (-mapcat 1410 (-lambda ((&Hover :contents)) 1411 (if (and (sequencep contents) 1412 (not (stringp contents))) 1413 (append contents ()) 1414 (list contents))) 1415 hovers))))) 1416 ("textDocument/completion" 1417 (lsp-make-completion-list 1418 :is-incomplete (seq-some 1419 #'lsp:completion-list-is-incomplete 1420 results) 1421 :items (cl-mapcan (lambda (it) (append (if (lsp-completion-list? it) 1422 (lsp:completion-list-items it) 1423 it) 1424 nil)) 1425 results))) 1426 ("completionItem/resolve" 1427 (let ((item (cl-first results))) 1428 (when-let ((details (seq-filter #'identity 1429 (seq-map #'lsp:completion-item-detail? results)))) 1430 (lsp:set-completion-item-detail? 1431 item 1432 (string-join details " "))) 1433 (when-let ((docs (seq-filter #'identity 1434 (seq-map #'lsp:completion-item-documentation? results)))) 1435 (lsp:set-completion-item-documentation? 1436 item 1437 (lsp-make-markup-content 1438 :kind (or (seq-some (lambda (it) 1439 (when (equal (lsp:markup-content-kind it) 1440 lsp/markup-kind-markdown) 1441 lsp/markup-kind-markdown)) 1442 docs) 1443 lsp/markup-kind-plain-text) 1444 :value (string-join (seq-map (lambda (doc) 1445 (or (lsp:markup-content-value doc) 1446 (and (stringp doc) doc))) 1447 docs) 1448 "\n")))) 1449 (when-let ((edits (seq-filter #'identity 1450 (seq-map #'lsp:completion-item-additional-text-edits? results)))) 1451 (lsp:set-completion-item-additional-text-edits? 1452 item 1453 (cl-mapcan (lambda (it) (if (seqp it) it (list it))) edits))) 1454 item)) 1455 (_ (cl-mapcan (lambda (it) (if (seqp it) it (list it))) results)))))) 1456 1457 (defun lsp--spinner-start () 1458 "Start spinner indication." 1459 (condition-case _err (spinner-start (lsp-progress-spinner-type)) (error))) 1460 1461 (defun lsp--propertize (str type) 1462 "Propertize STR as per TYPE." 1463 (propertize str 'face (alist-get type lsp--message-type-face))) 1464 1465 (defun lsp-workspaces () 1466 "Return the lsp workspaces associated with the current project." 1467 (if lsp--cur-workspace (list lsp--cur-workspace) lsp--buffer-workspaces)) 1468 1469 (defun lsp--completing-read (prompt collection transform-fn &optional predicate 1470 require-match initial-input 1471 hist def inherit-input-method) 1472 "Wrap `completing-read' to provide transformation function and disable sort. 1473 1474 TRANSFORM-FN will be used to transform each of the items before displaying. 1475 1476 PROMPT COLLECTION PREDICATE REQUIRE-MATCH INITIAL-INPUT HIST DEF 1477 INHERIT-INPUT-METHOD will be proxied to `completing-read' without changes." 1478 (let* ((col (--map (cons (funcall transform-fn it) it) collection)) 1479 (completion (completing-read prompt 1480 (lambda (string pred action) 1481 (if (eq action 'metadata) 1482 `(metadata (display-sort-function . identity)) 1483 (complete-with-action action col string pred))) 1484 predicate require-match initial-input hist 1485 def inherit-input-method))) 1486 (cdr (assoc completion col)))) 1487 1488 (defconst lsp--system-arch (lambda () 1489 (setq lsp--system-arch 1490 (pcase system-type 1491 ('windows-nt 1492 (pcase system-configuration 1493 ((rx bol "x86_64-") 'x64) 1494 (_ 'x86))) 1495 ('darwin 1496 (pcase system-configuration 1497 ((rx "aarch64-") 'arm64) 1498 (_ 'x64))) 1499 ('gnu/linux 1500 (pcase system-configuration 1501 ((rx bol "x86_64") 'x64) 1502 ((rx bol (| "i386" "i886")) 'x32))) 1503 (_ 1504 (pcase system-configuration 1505 ((rx bol "x86_64") 'x64) 1506 ((rx bol (| "i386" "i886")) 'x32)))))) 1507 "Return the system architecture of `Emacs'. 1508 Special values: 1509 `x64' 64bit 1510 `x32' 32bit 1511 `arm64' ARM 64bit") 1512 1513 (defmacro lsp-with-current-buffer (buffer-id &rest body) 1514 (declare (indent 1) (debug t)) 1515 `(if-let ((wcb (plist-get ,buffer-id :with-current-buffer))) 1516 (with-lsp-workspaces (plist-get ,buffer-id :workspaces) 1517 (funcall wcb (lambda () ,@body))) 1518 (with-current-buffer ,buffer-id 1519 ,@body))) 1520 1521 (defvar lsp--throw-on-input nil 1522 "Make `lsp-*-while-no-input' throws `input' on interrupted.") 1523 1524 (defmacro lsp--catch (tag bodyform &rest handlers) 1525 "Catch TAG thrown in BODYFORM. 1526 The return value from TAG will be handled in HANDLERS by `pcase'." 1527 (declare (debug (form form &rest (pcase-PAT body))) (indent 2)) 1528 (let ((re-sym (make-symbol "re"))) 1529 `(let ((,re-sym (catch ,tag ,bodyform))) 1530 (pcase ,re-sym 1531 ,@handlers)))) 1532 1533 (defmacro lsp--while-no-input (&rest body) 1534 "Wrap BODY in `while-no-input' and respecting `non-essential'. 1535 If `lsp--throw-on-input' is set, will throw if input is pending, else 1536 return value of `body' or nil if interrupted." 1537 (declare (debug t) (indent 0)) 1538 `(if non-essential 1539 (let ((res (while-no-input ,@body))) 1540 (cond 1541 ((and lsp--throw-on-input (equal res t)) 1542 (throw 'input :interrupted)) 1543 ((booleanp res) nil) 1544 (t res))) 1545 ,@body)) 1546 1547 ;; A ‘lsp--client’ object describes the client-side behavior of a language 1548 ;; server. It is used to start individual server processes, each of which is 1549 ;; represented by a ‘lsp--workspace’ object. Client objects are normally 1550 ;; created using ‘lsp-define-stdio-client’ or ‘lsp-define-tcp-client’. Each 1551 ;; workspace refers to exactly one client, but there can be multiple workspaces 1552 ;; for a single client. 1553 (cl-defstruct lsp--client 1554 ;; ‘language-id’ is a function that receives a buffer as a single argument 1555 ;; and should return the language identifier for that buffer. See 1556 ;; https://microsoft.github.io/language-server-protocol/specification#textdocumentitem 1557 ;; for a list of language identifiers. Also consult the documentation for 1558 ;; the language server represented by this client to find out what language 1559 ;; identifiers it supports or expects. 1560 (language-id nil) 1561 1562 ;; ‘add-on?’ when set to t the server will be started no matter whether there 1563 ;; is another server handling the same mode. 1564 (add-on? nil) 1565 ;; ‘new-connection’ is a function that should start a language server process 1566 ;; and return a cons (COMMAND-PROCESS . COMMUNICATION-PROCESS). 1567 ;; COMMAND-PROCESS must be a process object representing the server process 1568 ;; just started. COMMUNICATION-PROCESS must be a process (including pipe and 1569 ;; network processes) that ‘lsp-mode’ uses to communicate with the language 1570 ;; server using the language server protocol. COMMAND-PROCESS and 1571 ;; COMMUNICATION-PROCESS may be the same process; in that case 1572 ;; ‘new-connection’ may also return that process as a single 1573 ;; object. ‘new-connection’ is called with two arguments, FILTER and 1574 ;; SENTINEL. FILTER should be used as process filter for 1575 ;; COMMUNICATION-PROCESS, and SENTINEL should be used as process sentinel for 1576 ;; COMMAND-PROCESS. 1577 (new-connection nil) 1578 1579 ;; ‘ignore-regexps’ is a list of regexps. When a data packet from the 1580 ;; language server matches any of these regexps, it will be ignored. This is 1581 ;; intended for dealing with language servers that output non-protocol data. 1582 (ignore-regexps nil) 1583 1584 ;; ‘ignore-messages’ is a list of regexps. When a message from the language 1585 ;; server matches any of these regexps, it will be ignored. This is useful 1586 ;; for filtering out unwanted messages; such as servers that send nonstandard 1587 ;; message types, or extraneous log messages. 1588 (ignore-messages nil) 1589 1590 ;; ‘notification-handlers’ is a hash table mapping notification method names 1591 ;; (strings) to functions handling the respective notifications. Upon 1592 ;; receiving a notification, ‘lsp-mode’ will call the associated handler 1593 ;; function passing two arguments, the ‘lsp--workspace’ object and the 1594 ;; deserialized notification parameters. 1595 (notification-handlers (make-hash-table :test 'equal)) 1596 1597 ;; ‘request-handlers’ is a hash table mapping request method names 1598 ;; (strings) to functions handling the respective notifications. Upon 1599 ;; receiving a request, ‘lsp-mode’ will call the associated handler function 1600 ;; passing two arguments, the ‘lsp--workspace’ object and the deserialized 1601 ;; request parameters. 1602 (request-handlers (make-hash-table :test 'equal)) 1603 1604 ;; ‘response-handlers’ is a hash table mapping integral JSON-RPC request 1605 ;; identifiers for pending asynchronous requests to functions handling the 1606 ;; respective responses. Upon receiving a response from the language server, 1607 ;; ‘lsp-mode’ will call the associated response handler function with a 1608 ;; single argument, the deserialized response parameters. 1609 (response-handlers (make-hash-table :test 'eql)) 1610 1611 ;; ‘prefix-function’ is called for getting the prefix for completion. 1612 ;; The function takes no parameter and returns a cons (start . end) representing 1613 ;; the start and end bounds of the prefix. If it's not set, the client uses a 1614 ;; default prefix function." 1615 (prefix-function nil) 1616 1617 ;; Contains mapping of scheme to the function that is going to be used to load 1618 ;; the file. 1619 (uri-handlers (make-hash-table :test #'equal)) 1620 1621 ;; ‘action-handlers’ is a hash table mapping action to a handler function. It 1622 ;; can be used in `lsp-execute-code-action' to determine whether the action 1623 ;; current client is interested in executing the action instead of sending it 1624 ;; to the server. 1625 (action-handlers (make-hash-table :test 'equal)) 1626 1627 ;; major modes supported by the client. 1628 major-modes 1629 ;; Function that will be called to decide if this language client 1630 ;; should manage a particular buffer. The function will be passed 1631 ;; the file name and major mode to inform the decision. Setting 1632 ;; `activation-fn' will override `major-modes', if 1633 ;; present. 1634 activation-fn 1635 ;; Break the tie when major-mode is supported by multiple clients. 1636 (priority 0) 1637 ;; Unique identifier for representing the client object. 1638 server-id 1639 ;; defines whether the client supports multi root workspaces. 1640 multi-root 1641 ;; Initialization options or a function that returns initialization options. 1642 initialization-options 1643 ;; `semantic-tokens-faces-overrides’ is a plist that can be used to extend, or 1644 ;; completely replace, the faces used for semantic highlighting on a 1645 ;; client-by-client basis. 1646 ;; 1647 ;; It recognizes four members, all of which are optional: `:types’ and 1648 ;; `:modifiers’, respectively, should be face definition lists akin to 1649 ;; `:lsp-semantic-token-faces’. If specified, each of these face lists will be 1650 ;; merged with the default face definition list. 1651 ;; 1652 ;; Alternatively, if the plist members `:discard-default-types’ or 1653 ;; `:discard-default-modifiers' are non-nil, the default `:type' or `:modifiers' 1654 ;; face definitions will be replaced entirely by their respective overrides. 1655 ;; 1656 ;; For example, setting `:semantic-tokens-faces-overrides' to 1657 ;; `(:types (("macro" . font-lock-keyword-face)))' will remap "macro" tokens from 1658 ;; their default face `lsp-face-semhl-macro' to `font-lock-keyword-face'. 1659 ;; 1660 ;; `(:types (("macro" . font-lock-keyword-face) ("not-quite-a-macro" . some-face)))' 1661 ;; will also remap "macro", but on top of that associate the fictional token type 1662 ;; "not-quite-a-macro" with the face named `some-face'. 1663 ;; 1664 ;; `(:types (("macro" . font-lock-keyword-face)) 1665 ;; :modifiers (("declaration" . lsp-face-semhl-interface)) 1666 ;; :discard-default-types t 1667 ;; :discard-default-modifiers t)' 1668 ;; will discard all default face definitions, hence leaving the client with 1669 ;; only one token type "macro", mapped to `font-lock-keyword-face', and one 1670 ;; modifier type "declaration", mapped to `lsp-face-semhl-interface'. 1671 semantic-tokens-faces-overrides 1672 ;; Provides support for registering LSP Server specific capabilities. 1673 custom-capabilities 1674 ;; Function which returns the folders that are considered to be not projects but library files. 1675 ;; The function accepts one parameter currently active workspace. 1676 ;; See: https://github.com/emacs-lsp/lsp-mode/issues/225. 1677 library-folders-fn 1678 ;; function which will be called when opening file in the workspace to perform 1679 ;; client specific initialization. The function accepts one parameter 1680 ;; currently active workspace. 1681 before-file-open-fn 1682 ;; Function which will be called right after a workspace has been initialized. 1683 initialized-fn 1684 ;; ‘remote?’ indicate whether the client can be used for LSP server over TRAMP. 1685 (remote? nil) 1686 1687 ;; ‘completion-in-comments?’ t if the client supports completion in comments. 1688 (completion-in-comments? nil) 1689 1690 ;; ‘path->uri-fn’ the function to use for path->uri conversion for the client. 1691 (path->uri-fn nil) 1692 1693 ;; ‘uri->path-fn’ the function to use for uri->path conversion for the client. 1694 (uri->path-fn nil) 1695 ;; Function that returns an environment structure that will be used 1696 ;; to set some environment variables when starting the language 1697 ;; server process. These environment variables enable some 1698 ;; additional features in the language server. The environment 1699 ;; structure is an alist of the form (KEY . VALUE), where KEY is a 1700 ;; string (regularly in all caps), and VALUE may be a string, a 1701 ;; boolean, or a sequence of strings. 1702 environment-fn 1703 1704 ;; ‘after-open-fn’ workspace after open specific hooks. 1705 (after-open-fn nil) 1706 1707 ;; ‘async-request-handlers’ is a hash table mapping request method names 1708 ;; (strings) to functions handling the respective requests that may take 1709 ;; time to finish. Upon receiving a request, ‘lsp-mode’ will call the 1710 ;; associated handler function passing three arguments, the ‘lsp--workspace’ 1711 ;; object, the deserialized request parameters and the callback which accept 1712 ;; result as its parameter. 1713 (async-request-handlers (make-hash-table :test 'equal)) 1714 download-server-fn 1715 download-in-progress? 1716 buffers 1717 synchronize-sections) 1718 1719 (defun lsp-clients-executable-find (find-command &rest args) 1720 "Finds an executable by invoking a search command. 1721 1722 FIND-COMMAND is the executable finder that searches for the 1723 actual language server executable. ARGS is a list of arguments to 1724 give to FIND-COMMAND to find the language server. Returns the 1725 output of FIND-COMMAND if it exits successfully, nil otherwise. 1726 1727 Typical uses include finding an executable by invoking `find' in 1728 a project, finding LLVM commands on macOS with `xcrun', or 1729 looking up project-specific language servers for projects written 1730 in the various dynamic languages, e.g. `nvm', `pyenv' and `rbenv' 1731 etc." 1732 (when-let* ((find-command-path (executable-find find-command)) 1733 (executable-path 1734 (with-temp-buffer 1735 (when (zerop (apply 'call-process find-command-path nil t nil args)) 1736 (buffer-substring-no-properties (point-min) (point-max)))))) 1737 (string-trim executable-path))) 1738 1739 (defvar lsp--already-widened nil) 1740 1741 (defmacro lsp-save-restriction-and-excursion (&rest form) 1742 (declare (indent 0) (debug t)) 1743 `(if lsp--already-widened 1744 (save-excursion ,@form) 1745 (-let [lsp--already-widened t] 1746 (save-restriction 1747 (widen) 1748 (save-excursion ,@form))))) 1749 1750 ;; from http://emacs.stackexchange.com/questions/8082/how-to-get-buffer-position-given-line-number-and-column-number 1751 (defun lsp--line-character-to-point (line character) 1752 "Return the point for character CHARACTER on line LINE." 1753 (or (lsp-virtual-buffer-call :line/character->point line character) 1754 (let ((inhibit-field-text-motion t)) 1755 (lsp-save-restriction-and-excursion 1756 (goto-char (point-min)) 1757 (forward-line line) 1758 ;; server may send character position beyond the current line and we 1759 ;; should fallback to line end. 1760 (-let [line-end (line-end-position)] 1761 (if (> character (- line-end (point))) 1762 line-end 1763 (forward-char character) 1764 (point))))))) 1765 1766 (lsp-defun lsp--position-to-point ((&Position :line :character)) 1767 "Convert `Position' object in PARAMS to a point." 1768 (lsp--line-character-to-point line character)) 1769 1770 (lsp-defun lsp--range-to-region ((&RangeToPoint :start :end)) 1771 (cons start end)) 1772 1773 (lsp-defun lsp--range-text ((&RangeToPoint :start :end)) 1774 (buffer-substring start end)) 1775 1776 (lsp-defun lsp--find-wrapping-range ((&SelectionRange :parent? :range (&RangeToPoint :start :end))) 1777 (cond 1778 ((and 1779 (region-active-p) 1780 (<= start (region-beginning) end) 1781 (<= start (region-end) end) 1782 (or (not (= start (region-beginning))) 1783 (not (= end (region-end))))) 1784 (cons start end)) 1785 ((and (<= start (point) end) 1786 (not (region-active-p))) 1787 (cons start end)) 1788 (parent? (lsp--find-wrapping-range parent?)))) 1789 1790 (defun lsp--get-selection-range () 1791 (or 1792 (-when-let ((cache . cache-tick) lsp--document-selection-range-cache) 1793 (when (= cache-tick (buffer-modified-tick)) cache)) 1794 (let ((response (cl-first 1795 (lsp-request 1796 "textDocument/selectionRange" 1797 (list :textDocument (lsp--text-document-identifier) 1798 :positions (vector (lsp--cur-position))))))) 1799 (setq lsp--document-selection-range-cache 1800 (cons response (buffer-modified-tick))) 1801 response))) 1802 1803 (defun lsp-extend-selection () 1804 "Extend selection." 1805 (interactive) 1806 (unless (lsp-feature? "textDocument/selectionRange") 1807 (signal 'lsp-capability-not-supported (list "selectionRangeProvider"))) 1808 (-when-let ((start . end) (lsp--find-wrapping-range (lsp--get-selection-range))) 1809 (goto-char start) 1810 (set-mark (point)) 1811 (goto-char end) 1812 (exchange-point-and-mark))) 1813 1814 (defun lsp-warn (message &rest args) 1815 "Display a warning message made from (`format-message' MESSAGE ARGS...). 1816 This is equivalent to `display-warning', using `lsp-mode' as the type and 1817 `:warning' as the level." 1818 (display-warning 'lsp-mode (apply #'format-message message args))) 1819 1820 (defun lsp--get-uri-handler (scheme) 1821 "Get uri handler for SCHEME in the current workspace." 1822 (--some (gethash scheme (lsp--client-uri-handlers (lsp--workspace-client it))) 1823 (or (lsp-workspaces) (lsp--session-workspaces (lsp-session))))) 1824 1825 (defun lsp--fix-path-casing (path) 1826 "On windows, downcases path because the windows file system is 1827 case-insensitive. 1828 1829 On other systems, returns path without change." 1830 (if (eq system-type 'windows-nt) (downcase path) path)) 1831 1832 (defun lsp--uri-to-path (uri) 1833 "Convert URI to a file path." 1834 (if-let ((fn (->> (lsp-workspaces) 1835 (-keep (-compose #'lsp--client-uri->path-fn #'lsp--workspace-client)) 1836 (cl-first)))) 1837 (funcall fn uri) 1838 (lsp--uri-to-path-1 uri))) 1839 1840 (defun lsp-remap-path-if-needed (file-name) 1841 (-if-let ((virtual-buffer &as &plist :buffer) (gethash file-name lsp--virtual-buffer-mappings)) 1842 (propertize (buffer-local-value 'buffer-file-name buffer) 1843 'lsp-virtual-buffer virtual-buffer) 1844 file-name)) 1845 1846 (defun lsp--uri-to-path-1 (uri) 1847 "Convert URI to a file path." 1848 (let* ((url (url-generic-parse-url (url-unhex-string uri))) 1849 (type (url-type url)) 1850 (target (url-target url)) 1851 (file 1852 (concat (decode-coding-string (url-filename url) 1853 (or locale-coding-system 'utf-8)) 1854 (when (and target 1855 (not (s-match 1856 (rx "#" (group (1+ num)) (or "," "#") 1857 (group (1+ num)) 1858 string-end) 1859 uri))) 1860 (concat "#" target)))) 1861 (file-name (if (and type (not (string= type "file"))) 1862 (if-let ((handler (lsp--get-uri-handler type))) 1863 (funcall handler uri) 1864 uri) 1865 ;; `url-generic-parse-url' is buggy on windows: 1866 ;; https://github.com/emacs-lsp/lsp-mode/pull/265 1867 (or (and (eq system-type 'windows-nt) 1868 (eq (elt file 0) ?\/) 1869 (substring file 1)) 1870 file)))) 1871 (->> file-name 1872 (concat (-some #'lsp--workspace-host-root (lsp-workspaces))) 1873 (lsp-remap-path-if-needed)))) 1874 1875 (defun lsp--buffer-uri () 1876 "Return URI of the current buffer." 1877 (or lsp-buffer-uri 1878 (plist-get lsp--virtual-buffer :buffer-uri) 1879 (lsp--path-to-uri 1880 (or (buffer-file-name) (buffer-file-name (buffer-base-buffer)))))) 1881 1882 (defun lsp-register-client-capabilities (&rest _args) 1883 "Implemented only to make `company-lsp' happy. 1884 DELETE when `lsp-mode.el' is deleted.") 1885 1886 (defconst lsp--url-path-allowed-chars 1887 (url--allowed-chars (append '(?/) url-unreserved-chars)) 1888 "`url-unreserved-chars' with additional delim ?/. 1889 This set of allowed chars is enough for hexifying local file paths.") 1890 1891 (defun lsp--path-to-uri-1 (path) 1892 (concat lsp--uri-file-prefix 1893 (--> path 1894 (expand-file-name it) 1895 (or (file-remote-p it 'localname t) it) 1896 (url-hexify-string it lsp--url-path-allowed-chars)))) 1897 1898 (defun lsp--path-to-uri (path) 1899 "Convert PATH to a uri." 1900 (if-let ((uri-fn (->> (lsp-workspaces) 1901 (-keep (-compose #'lsp--client-path->uri-fn #'lsp--workspace-client)) 1902 (cl-first)))) 1903 (funcall uri-fn path) 1904 (lsp--path-to-uri-1 path))) 1905 1906 (defun lsp--string-match-any (regex-list str) 1907 "Return the first regex, if any, within REGEX-LIST matching STR." 1908 (--first (string-match it str) regex-list)) 1909 1910 (cl-defstruct lsp-watch 1911 (descriptors (make-hash-table :test 'equal)) 1912 root-directory) 1913 1914 (defun lsp--folder-watch-callback (event callback watch ignored-files ignored-directories) 1915 (let ((file-name (cl-third event)) 1916 (event-type (cl-second event))) 1917 (cond 1918 ((and (file-directory-p file-name) 1919 (equal 'created event-type) 1920 (not (lsp--string-match-any ignored-directories file-name))) 1921 1922 (lsp-watch-root-folder (file-truename file-name) callback ignored-files ignored-directories watch) 1923 1924 ;; process the files that are already present in 1925 ;; the directory. 1926 (->> (directory-files-recursively file-name ".*" t) 1927 (seq-do (lambda (f) 1928 (unless (file-directory-p f) 1929 (funcall callback (list nil 'created f))))))) 1930 ((and (memq event-type '(created deleted changed)) 1931 (not (file-directory-p file-name)) 1932 (not (lsp--string-match-any ignored-files file-name))) 1933 (funcall callback event)) 1934 ((and (memq event-type '(renamed)) 1935 (not (file-directory-p file-name)) 1936 (not (lsp--string-match-any ignored-files file-name))) 1937 (funcall callback `(,(cl-first event) deleted ,(cl-third event))) 1938 (funcall callback `(,(cl-first event) created ,(cl-fourth event))))))) 1939 1940 (defun lsp--ask-about-watching-big-repo (number-of-directories dir) 1941 "Ask the user if they want to watch NUMBER-OF-DIRECTORIES from a repository DIR. 1942 This is useful when there is a lot of files in a repository, as 1943 that may slow Emacs down. Returns t if the user wants to watch 1944 the entire repository, nil otherwise." 1945 (prog1 1946 (yes-or-no-p 1947 (format 1948 "Watching all the files in %s would require adding watches to %s directories, so watching the repo may slow Emacs down. 1949 Do you want to watch all files in %s? " 1950 dir 1951 number-of-directories 1952 dir)) 1953 (lsp--info 1954 (concat "You can configure this warning with the `lsp-enable-file-watchers' " 1955 "and `lsp-file-watch-threshold' variables")))) 1956 1957 1958 (defun lsp--path-is-watchable-directory (path dir ignored-directories) 1959 "Figure out whether PATH (inside of DIR) is meant to have a file watcher set. 1960 IGNORED-DIRECTORIES is a list of regexes to filter out directories we don't 1961 want to watch." 1962 (let 1963 ((full-path (f-join dir path))) 1964 (and (file-accessible-directory-p full-path) 1965 (not (equal path ".")) 1966 (not (equal path "..")) 1967 (not (lsp--string-match-any ignored-directories full-path))))) 1968 1969 1970 (defun lsp--all-watchable-directories (dir ignored-directories) 1971 "Traverse DIR recursively returning a list of paths that should have watchers. 1972 IGNORED-DIRECTORIES will be used for exclusions" 1973 (let* ((dir (if (f-symlink? dir) 1974 (file-truename dir) 1975 dir))) 1976 (apply #'nconc 1977 ;; the directory itself is assumed to be part of the set 1978 (list dir) 1979 ;; collect all subdirectories that are watchable 1980 (-map 1981 (lambda (path) (lsp--all-watchable-directories (f-join dir path) ignored-directories)) 1982 ;; but only look at subdirectories that are watchable 1983 (-filter (lambda (path) (lsp--path-is-watchable-directory path dir ignored-directories)) 1984 (directory-files dir)))))) 1985 1986 (defun lsp-watch-root-folder (dir callback ignored-files ignored-directories &optional watch warn-big-repo?) 1987 "Create recursive file notification watch in DIR. 1988 CALLBACK will be called when there are changes in any of 1989 the monitored files. WATCHES is a hash table directory->file 1990 notification handle which contains all of the watch that 1991 already have been created. Watches will not be created for 1992 any directory that matches any regex in IGNORED-DIRECTORIES. 1993 Watches will not be created for any file that matches any 1994 regex in IGNORED-FILES." 1995 (let* ((dir (if (f-symlink? dir) 1996 (file-truename dir) 1997 dir)) 1998 (watch (or watch (make-lsp-watch :root-directory dir))) 1999 (dirs-to-watch (lsp--all-watchable-directories dir ignored-directories))) 2000 (lsp-log "Creating watchers for following %s folders:\n %s" 2001 (length dirs-to-watch) 2002 (s-join "\n " dirs-to-watch)) 2003 (when (or 2004 (not warn-big-repo?) 2005 (not lsp-file-watch-threshold) 2006 (let ((number-of-directories (length dirs-to-watch))) 2007 (or 2008 (< number-of-directories lsp-file-watch-threshold) 2009 (condition-case nil 2010 (lsp--ask-about-watching-big-repo number-of-directories dir) 2011 (quit))))) 2012 (dolist (current-dir dirs-to-watch) 2013 (condition-case err 2014 (progn 2015 (puthash 2016 current-dir 2017 (file-notify-add-watch current-dir 2018 '(change) 2019 (lambda (event) 2020 (lsp--folder-watch-callback event callback watch ignored-files ignored-directories))) 2021 (lsp-watch-descriptors watch))) 2022 (error (lsp-log "Failed to create a watch for %s: message" (error-message-string err))) 2023 (file-missing (lsp-log "Failed to create a watch for %s: message" (error-message-string err)))))) 2024 watch)) 2025 2026 (defun lsp-kill-watch (watch) 2027 "Delete WATCH." 2028 (-> watch lsp-watch-descriptors hash-table-values (-each #'file-notify-rm-watch)) 2029 (ht-clear! (lsp-watch-descriptors watch))) 2030 2031 (defun lsp-json-bool (val) 2032 "Convert VAL to JSON boolean." 2033 (if val t :json-false)) 2034 2035 (defmacro with-lsp-workspace (workspace &rest body) 2036 "Helper macro for invoking BODY in WORKSPACE context." 2037 (declare (debug (form body)) 2038 (indent 1)) 2039 `(let ((lsp--cur-workspace ,workspace)) ,@body)) 2040 2041 (defmacro with-lsp-workspaces (workspaces &rest body) 2042 "Helper macro for invoking BODY against multiple WORKSPACES." 2043 (declare (debug (form body)) 2044 (indent 1)) 2045 `(let ((lsp--buffer-workspaces ,workspaces)) ,@body)) 2046 2047 2048 2049 (defmacro lsp-consistency-check (package) 2050 `(defconst ,(intern (concat (symbol-name package) 2051 "-plist-value-when-compiled")) 2052 (eval-when-compile lsp-use-plists))) 2053 2054 2055 ;; loading code-workspace files 2056 2057 ;;;###autoload 2058 (defun lsp-load-vscode-workspace (file) 2059 "Load vscode workspace from FILE" 2060 (interactive "fSelect file to import: ") 2061 (mapc #'lsp-workspace-folders-remove (lsp-session-folders (lsp-session))) 2062 2063 (let ((dir (f-dirname file))) 2064 (->> file 2065 (json-read-file) 2066 (alist-get 'folders) 2067 (-map (-lambda ((&alist 'path)) 2068 (lsp-workspace-folders-add (expand-file-name path dir))))))) 2069 2070 ;;;###autoload 2071 (defun lsp-save-vscode-workspace (file) 2072 "Save vscode workspace to FILE" 2073 (interactive "FSelect file to save to: ") 2074 2075 (let ((json-encoding-pretty-print t)) 2076 (f-write-text (json-encode 2077 `((folders . ,(->> (lsp-session) 2078 (lsp-session-folders) 2079 (--map `((path . ,it))))))) 2080 'utf-8 2081 file))) 2082 2083 2084 (defmacro lsp-foreach-workspace (&rest body) 2085 "Execute BODY for each of the current workspaces." 2086 (declare (debug (form body))) 2087 `(--map (with-lsp-workspace it ,@body) (lsp-workspaces))) 2088 2089 (defmacro when-lsp-workspace (workspace &rest body) 2090 "Helper macro for invoking BODY in WORKSPACE context if present." 2091 (declare (debug (form body)) 2092 (indent 1)) 2093 `(when-let ((lsp--cur-workspace ,workspace)) ,@body)) 2094 2095 (lsp-defun lsp--window-show-quick-pick (_workspace (&ShowQuickPickParams :place-holder :can-pick-many :items)) 2096 (if-let* ((selectfunc (if can-pick-many #'completing-read-multiple #'completing-read)) 2097 (itemLabels (seq-map (-lambda ((item &as &QuickPickItem :label)) (format "%s" label)) 2098 items)) 2099 (result (funcall-interactively 2100 selectfunc 2101 (format "%s%s " place-holder (if can-pick-many " (* for all)" "")) itemLabels)) 2102 (choices (if (listp result) 2103 (if (equal result '("*")) 2104 itemLabels 2105 result) 2106 (list result)))) 2107 (vconcat (seq-filter #'identity (seq-map (-lambda ((item &as &QuickPickItem :label :user-data)) 2108 (if (member label choices) 2109 (lsp-make-quick-pick-item :label label :picked t :user-data user-data) 2110 nil)) 2111 items))))) 2112 2113 (lsp-defun lsp--window-show-input-box (_workspace (&ShowInputBoxParams :prompt :value?)) 2114 (read-string (format "%s: " prompt) (or value? ""))) 2115 2116 (lsp-defun lsp--window-show-message (_workspace (&ShowMessageRequestParams :message :type)) 2117 "Send the server's messages to log. 2118 PARAMS - the data sent from _WORKSPACE." 2119 (funcall (cl-case type 2120 (1 'lsp--error) 2121 (2 'lsp--warn) 2122 (t 'lsp--info)) 2123 "%s" 2124 message)) 2125 2126 (lsp-defun lsp--window-log-message (workspace (&ShowMessageRequestParams :message :type)) 2127 "Send the server's messages to log. 2128 PARAMS - the data sent from WORKSPACE." 2129 (ignore 2130 (let ((client (lsp--workspace-client workspace))) 2131 (when (or (not client) 2132 (cl-notany (-rpartial #'string-match-p message) 2133 (lsp--client-ignore-messages client))) 2134 (lsp-log "%s" (lsp--propertize message type)))))) 2135 2136 (lsp-defun lsp--window-log-message-request ((&ShowMessageRequestParams :message :type :actions?)) 2137 "Display a message request to user sending the user selection back to server." 2138 (let* ((message (lsp--propertize message type)) 2139 (choices (seq-map #'lsp:message-action-item-title actions?))) 2140 (if choices 2141 (completing-read (concat message " ") (seq-into choices 'list) nil t) 2142 (lsp-log message)))) 2143 2144 (lsp-defun lsp--window-show-document ((&ShowDocumentParams :uri :selection?)) 2145 "Show document URI in a buffer and go to SELECTION if any." 2146 (let ((path (lsp--uri-to-path uri))) 2147 (when (f-exists? path) 2148 (with-current-buffer (find-file path) 2149 (when selection? 2150 (goto-char (lsp--position-to-point (lsp:range-start selection?)))) 2151 t)))) 2152 2153 (defcustom lsp-progress-prefix " ⌛ " 2154 "Progress prefix." 2155 :group 'lsp-mode 2156 :type 'string 2157 :package-version '(lsp-mode . "8.0.0")) 2158 2159 (defcustom lsp-progress-function #'lsp-on-progress-modeline 2160 "Function for handling the progress notifications." 2161 :group 'lsp-mode 2162 :type '(choice 2163 (const :tag "Use modeline" lsp-on-progress-modeline) 2164 (const :tag "Legacy(uses either `progress-reporter' or `spinner' based on `lsp-progress-via-spinner')" 2165 lsp-on-progress-legacy) 2166 (const :tag "Ignore" ignore) 2167 (function :tag "Other function")) 2168 :package-version '(lsp-mode . "8.0.0")) 2169 2170 (defcustom lsp-request-while-no-input-may-block nil 2171 "Have `lsp-request-while-no-input` block unless `non-essential` is t." 2172 :group 'lsp-mode 2173 :type 'boolean) 2174 2175 (defun lsp--progress-status () 2176 "Returns the status of the progress for the current workspaces." 2177 (-let ((progress-status 2178 (s-join 2179 "|" 2180 (-keep 2181 (lambda (workspace) 2182 (let ((tokens (lsp--workspace-work-done-tokens workspace))) 2183 (unless (ht-empty? tokens) 2184 (mapconcat 2185 (-lambda ((&WorkDoneProgressBegin :message? :title :percentage?)) 2186 (concat (if percentage? 2187 (if (numberp percentage?) 2188 (format "%.0f%%%% " percentage?) 2189 (format "%s%%%% " percentage?)) 2190 "") 2191 (or message? title))) 2192 (ht-values tokens) 2193 "|")))) 2194 (lsp-workspaces))))) 2195 (unless (s-blank? progress-status) 2196 (concat lsp-progress-prefix progress-status)))) 2197 2198 (lsp-defun lsp-on-progress-modeline (workspace (&ProgressParams :token :value 2199 (value &as &WorkDoneProgress :kind))) 2200 "PARAMS contains the progress data. 2201 WORKSPACE is the workspace that contains the progress token." 2202 (add-to-list 'global-mode-string '(t (:eval (lsp--progress-status)))) 2203 (pcase kind 2204 ("begin" (lsp-workspace-set-work-done-token token value workspace)) 2205 ("report" (lsp-workspace-set-work-done-token token value workspace)) 2206 ("end" (lsp-workspace-rem-work-done-token token workspace))) 2207 (force-mode-line-update)) 2208 2209 (lsp-defun lsp-on-progress-legacy (workspace (&ProgressParams :token :value 2210 (value &as &WorkDoneProgress :kind))) 2211 "PARAMS contains the progress data. 2212 WORKSPACE is the workspace that contains the progress token." 2213 (pcase kind 2214 ("begin" 2215 (-let* (((&WorkDoneProgressBegin :title :percentage?) value) 2216 (reporter 2217 (if lsp-progress-via-spinner 2218 (let* ((spinner-strings (alist-get (lsp-progress-spinner-type) spinner-types)) 2219 ;; Set message as a tooltip for the spinner strings 2220 (propertized-strings 2221 (seq-map (lambda (string) (propertize string 'help-echo title)) 2222 spinner-strings)) 2223 (spinner-type (vconcat propertized-strings))) 2224 ;; The progress relates to the server as a whole, 2225 ;; display it on all buffers. 2226 (mapcar (lambda (buffer) 2227 (lsp-with-current-buffer buffer 2228 (spinner-start spinner-type)) 2229 buffer) 2230 (lsp--workspace-buffers workspace))) 2231 (if percentage? 2232 (make-progress-reporter title 0 100 percentage?) 2233 ;; No percentage, just progress 2234 (make-progress-reporter title nil nil))))) 2235 (lsp-workspace-set-work-done-token token reporter workspace))) 2236 ("report" 2237 (when-let ((reporter (lsp-workspace-get-work-done-token token workspace))) 2238 (unless lsp-progress-via-spinner 2239 (progress-reporter-update reporter (lsp:work-done-progress-report-percentage? value))))) 2240 2241 ("end" 2242 (when-let ((reporter (lsp-workspace-get-work-done-token token workspace))) 2243 (if lsp-progress-via-spinner 2244 (mapc (lambda (buffer) 2245 (when (lsp-buffer-live-p buffer) 2246 (lsp-with-current-buffer buffer 2247 (spinner-stop)))) 2248 reporter) 2249 (progress-reporter-done reporter)) 2250 (lsp-workspace-rem-work-done-token token workspace))))) 2251 2252 2253 ;; diagnostics 2254 2255 (defvar lsp-diagnostic-filter nil 2256 "A a function which will be called with 2257 `&PublishDiagnosticsParams' and `workspace' which can be used 2258 to filter out the diagnostics. The function should return 2259 `&PublishDiagnosticsParams'. 2260 2261 Common usecase are: 2262 1. Filter the diagnostics for a particular language server. 2263 2. Filter out the diagnostics under specific level.") 2264 2265 (defvar lsp-diagnostic-stats (ht)) 2266 2267 (defun lsp-diagnostics (&optional current-workspace?) 2268 "Return the diagnostics from all workspaces." 2269 (or (pcase (if current-workspace? 2270 (lsp-workspaces) 2271 (lsp--session-workspaces (lsp-session))) 2272 (`() ()) 2273 (`(,workspace) (lsp--workspace-diagnostics workspace)) 2274 (`,workspaces (let ((result (make-hash-table :test 'equal))) 2275 (mapc (lambda (workspace) 2276 (->> workspace 2277 (lsp--workspace-diagnostics) 2278 (maphash (lambda (file-name diagnostics) 2279 (puthash file-name 2280 (append (gethash file-name result) diagnostics) 2281 result))))) 2282 workspaces) 2283 result))) 2284 (ht))) 2285 2286 (defun lsp-diagnostics-stats-for (path) 2287 "Get diagnostics statistics for PATH. 2288 The result format is vector [_ errors warnings infos hints] or nil." 2289 (gethash (lsp--fix-path-casing path) lsp-diagnostic-stats)) 2290 2291 (defun lsp-diagnostics--update-path (path new-stats) 2292 (let ((new-stats (copy-sequence new-stats)) 2293 (path (lsp--fix-path-casing (directory-file-name path)))) 2294 (if-let ((old-data (gethash path lsp-diagnostic-stats))) 2295 (dotimes (idx 5) 2296 (cl-callf + (aref old-data idx) 2297 (aref new-stats idx))) 2298 (puthash path new-stats lsp-diagnostic-stats)))) 2299 2300 (lsp-defun lsp--on-diagnostics-update-stats (workspace 2301 (&PublishDiagnosticsParams :uri :diagnostics)) 2302 (let ((path (lsp--fix-path-casing (lsp--uri-to-path uri))) 2303 (new-stats (make-vector 5 0))) 2304 (mapc (-lambda ((&Diagnostic :severity?)) 2305 (cl-incf (aref new-stats (or severity? 1)))) 2306 diagnostics) 2307 (when-let ((old-diags (gethash path (lsp--workspace-diagnostics workspace)))) 2308 (mapc (-lambda ((&Diagnostic :severity?)) 2309 (cl-decf (aref new-stats (or severity? 1)))) 2310 old-diags)) 2311 (lsp-diagnostics--update-path path new-stats) 2312 (while (not (string= path (setf path (file-name-directory 2313 (directory-file-name path))))) 2314 (lsp-diagnostics--update-path path new-stats)))) 2315 2316 (defun lsp--on-diagnostics (workspace params) 2317 "Callback for textDocument/publishDiagnostics. 2318 interface PublishDiagnosticsParams { 2319 uri: string; 2320 diagnostics: Diagnostic[]; 2321 } 2322 PARAMS contains the diagnostics data. 2323 WORKSPACE is the workspace that contains the diagnostics." 2324 (when lsp-diagnostic-filter 2325 (setf params (funcall lsp-diagnostic-filter params workspace))) 2326 2327 (lsp--on-diagnostics-update-stats workspace params) 2328 2329 (-let* (((&PublishDiagnosticsParams :uri :diagnostics) params) 2330 (lsp--virtual-buffer-mappings (ht)) 2331 (file (lsp--fix-path-casing (lsp--uri-to-path uri))) 2332 (workspace-diagnostics (lsp--workspace-diagnostics workspace))) 2333 2334 (if (seq-empty-p diagnostics) 2335 (remhash file workspace-diagnostics) 2336 (puthash file (append diagnostics nil) workspace-diagnostics)) 2337 2338 (run-hooks 'lsp-diagnostics-updated-hook))) 2339 2340 (defun lsp-diagnostics--workspace-cleanup (workspace) 2341 (->> workspace 2342 (lsp--workspace-diagnostics) 2343 (maphash (lambda (key _) 2344 (lsp--on-diagnostics-update-stats 2345 workspace 2346 (lsp-make-publish-diagnostics-params 2347 :uri (lsp--path-to-uri key) 2348 :diagnostics []))))) 2349 (clrhash (lsp--workspace-diagnostics workspace))) 2350 2351 2352 2353 ;; textDocument/foldingRange support 2354 2355 (cl-defstruct lsp--folding-range beg end kind children) 2356 2357 (defvar-local lsp--cached-folding-ranges nil) 2358 (defvar-local lsp--cached-nested-folding-ranges nil) 2359 2360 (defun lsp--folding-range-width (range) 2361 (- (lsp--folding-range-end range) 2362 (lsp--folding-range-beg range))) 2363 2364 (defun lsp--get-folding-ranges () 2365 "Get the folding ranges for the current buffer." 2366 (unless (eq (buffer-chars-modified-tick) (car lsp--cached-folding-ranges)) 2367 (let* ((ranges (lsp-request "textDocument/foldingRange" 2368 `(:textDocument ,(lsp--text-document-identifier)))) 2369 (sorted-line-col-pairs (->> ranges 2370 (cl-mapcan (-lambda ((&FoldingRange :start-line 2371 :start-character? 2372 :end-line 2373 :end-character?)) 2374 (list (cons start-line start-character?) 2375 (cons end-line end-character?)))) 2376 (-sort #'lsp--line-col-comparator))) 2377 (line-col-to-point-map (lsp--convert-line-col-to-points-batch 2378 sorted-line-col-pairs))) 2379 (setq lsp--cached-folding-ranges 2380 (cons (buffer-chars-modified-tick) 2381 (--> ranges 2382 (seq-map (-lambda ((range &as 2383 &FoldingRange :start-line 2384 :start-character? 2385 :end-line 2386 :end-character? 2387 :kind?)) 2388 (make-lsp--folding-range 2389 :beg (ht-get line-col-to-point-map 2390 (cons start-line start-character?)) 2391 :end (ht-get line-col-to-point-map 2392 (cons end-line end-character?)) 2393 :kind kind?)) 2394 it) 2395 (seq-filter (lambda (folding-range) 2396 (< (lsp--folding-range-beg folding-range) 2397 (lsp--folding-range-end folding-range))) 2398 it) 2399 (seq-into it 'list) 2400 (delete-dups it)))))) 2401 (cdr lsp--cached-folding-ranges)) 2402 2403 (defun lsp--get-nested-folding-ranges () 2404 "Get a list of nested folding ranges for the current buffer." 2405 (-let [(tick . _) lsp--cached-folding-ranges] 2406 (if (and (eq tick (buffer-chars-modified-tick)) 2407 lsp--cached-nested-folding-ranges) 2408 lsp--cached-nested-folding-ranges 2409 (setq lsp--cached-nested-folding-ranges 2410 (lsp--folding-range-build-trees (lsp--get-folding-ranges)))))) 2411 2412 (defun lsp--folding-range-build-trees (ranges) 2413 (setq ranges (seq-sort #'lsp--range-before-p ranges)) 2414 (let* ((dummy-node (make-lsp--folding-range 2415 :beg most-negative-fixnum 2416 :end most-positive-fixnum)) 2417 (stack (list dummy-node))) 2418 (dolist (range ranges) 2419 (while (not (lsp--range-inside-p range (car stack))) 2420 (pop stack)) 2421 (push range (lsp--folding-range-children (car stack))) 2422 (push range stack)) 2423 (lsp--folding-range-children dummy-node))) 2424 2425 (defun lsp--range-inside-p (r1 r2) 2426 "Return non-nil if folding range R1 lies inside R2" 2427 (and (>= (lsp--folding-range-beg r1) (lsp--folding-range-beg r2)) 2428 (<= (lsp--folding-range-end r1) (lsp--folding-range-end r2)))) 2429 2430 (defun lsp--range-before-p (r1 r2) 2431 "Return non-nil if folding range R1 ends before R2" 2432 ;; Ensure r1 comes before r2 2433 (or (< (lsp--folding-range-beg r1) 2434 (lsp--folding-range-beg r2)) 2435 ;; If beg(r1) == beg(r2) make sure r2 ends first 2436 (and (= (lsp--folding-range-beg r1) 2437 (lsp--folding-range-beg r2)) 2438 (< (lsp--folding-range-end r2) 2439 (lsp--folding-range-end r1))))) 2440 2441 (defun lsp--point-inside-range-p (point range) 2442 "Return non-nil if POINT lies inside folding range RANGE." 2443 (and (>= point (lsp--folding-range-beg range)) 2444 (<= point (lsp--folding-range-end range)))) 2445 2446 (cl-defun lsp--get-current-innermost-folding-range (&optional (point (point))) 2447 "Return the innermost folding range POINT lies in." 2448 (seq-reduce (lambda (innermost-range curr-range) 2449 (if (and (lsp--point-inside-range-p point curr-range) 2450 (or (null innermost-range) 2451 (lsp--range-inside-p curr-range innermost-range))) 2452 curr-range 2453 innermost-range)) 2454 (lsp--get-folding-ranges) 2455 nil)) 2456 2457 (cl-defun lsp--get-current-outermost-folding-range (&optional (point (point))) 2458 "Return the outermost folding range POINT lies in." 2459 (cdr (seq-reduce (-lambda ((best-pair &as outermost-width . _) curr-range) 2460 (let ((curr-width (lsp--folding-range-width curr-range))) 2461 (if (and (lsp--point-inside-range-p point curr-range) 2462 (or (null best-pair) 2463 (> curr-width outermost-width))) 2464 (cons curr-width curr-range) 2465 best-pair))) 2466 (lsp--get-folding-ranges) 2467 nil))) 2468 2469 (defun lsp--folding-range-at-point-bounds () 2470 (when (and lsp-enable-folding 2471 (lsp-feature? "textDocument/foldingRange")) 2472 (if-let ((range (lsp--get-current-innermost-folding-range))) 2473 (cons (lsp--folding-range-beg range) 2474 (lsp--folding-range-end range))))) 2475 (put 'lsp--folding-range 'bounds-of-thing-at-point 2476 #'lsp--folding-range-at-point-bounds) 2477 2478 (defun lsp--get-nearest-folding-range (&optional backward) 2479 (let ((point (point)) 2480 (found nil)) 2481 (while (not 2482 (or found 2483 (if backward 2484 (<= point (point-min)) 2485 (>= point (point-max))))) 2486 (if backward (cl-decf point) (cl-incf point)) 2487 (setq found (lsp--get-current-innermost-folding-range point))) 2488 found)) 2489 2490 (defun lsp--folding-range-at-point-forward-op (n) 2491 (when (and lsp-enable-folding 2492 (not (zerop n)) 2493 (lsp-feature? "textDocument/foldingRange")) 2494 (cl-block break 2495 (dotimes (_ (abs n)) 2496 (if-let ((range (lsp--get-nearest-folding-range (< n 0)))) 2497 (goto-char (if (< n 0) 2498 (lsp--folding-range-beg range) 2499 (lsp--folding-range-end range))) 2500 (cl-return-from break)))))) 2501 (put 'lsp--folding-range 'forward-op 2502 #'lsp--folding-range-at-point-forward-op) 2503 2504 (defun lsp--folding-range-at-point-beginning-op () 2505 (goto-char (car (lsp--folding-range-at-point-bounds)))) 2506 (put 'lsp--folding-range 'beginning-op 2507 #'lsp--folding-range-at-point-beginning-op) 2508 2509 (defun lsp--folding-range-at-point-end-op () 2510 (goto-char (cdr (lsp--folding-range-at-point-bounds)))) 2511 (put 'lsp--folding-range 'end-op 2512 #'lsp--folding-range-at-point-end-op) 2513 2514 (defun lsp--range-at-point-bounds () 2515 (or (lsp--folding-range-at-point-bounds) 2516 (when-let ((range (and 2517 (lsp-feature? "textDocument/hover") 2518 (->> (lsp--text-document-position-params) 2519 (lsp-request "textDocument/hover") 2520 (lsp:hover-range?))))) 2521 (lsp--range-to-region range)))) 2522 2523 ;; A more general purpose "thing", useful for applications like focus.el 2524 (put 'lsp--range 'bounds-of-thing-at-point 2525 #'lsp--range-at-point-bounds) 2526 2527 (defun lsp--log-io-p (method) 2528 "Return non nil if should log for METHOD." 2529 (and lsp-log-io 2530 (or (not lsp-log-io-allowlist-methods) 2531 (member method lsp-log-io-allowlist-methods)))) 2532 2533 2534 ;; toggles 2535 2536 (defun lsp-toggle-trace-io () 2537 "Toggle client-server protocol logging." 2538 (interactive) 2539 (setq lsp-log-io (not lsp-log-io)) 2540 (lsp--info "Server logging %s." (if lsp-log-io "enabled" "disabled"))) 2541 2542 (defun lsp-toggle-signature-auto-activate () 2543 "Toggle signature auto activate." 2544 (interactive) 2545 (setq lsp-signature-auto-activate 2546 (unless lsp-signature-auto-activate '(:on-trigger-char))) 2547 (lsp--info "Signature autoactivate %s." (if lsp-signature-auto-activate "enabled" "disabled")) 2548 (lsp--update-signature-help-hook)) 2549 2550 (defun lsp-toggle-on-type-formatting () 2551 "Toggle on type formatting." 2552 (interactive) 2553 (setq lsp-enable-on-type-formatting (not lsp-enable-on-type-formatting)) 2554 (lsp--info "On type formatting is %s." (if lsp-enable-on-type-formatting "enabled" "disabled")) 2555 (lsp--update-on-type-formatting-hook)) 2556 2557 (defun lsp-toggle-symbol-highlight () 2558 "Toggle symbol highlighting." 2559 (interactive) 2560 (setq lsp-enable-symbol-highlighting (not lsp-enable-symbol-highlighting)) 2561 2562 (cond 2563 ((and lsp-enable-symbol-highlighting 2564 (lsp-feature? "textDocument/documentHighlight")) 2565 (add-hook 'lsp-on-idle-hook #'lsp--document-highlight nil t) 2566 (lsp--info "Symbol highlighting enabled in current buffer.")) 2567 ((not lsp-enable-symbol-highlighting) 2568 (remove-hook 'lsp-on-idle-hook #'lsp--document-highlight t) 2569 (lsp--remove-overlays 'lsp-highlight) 2570 (lsp--info "Symbol highlighting disabled in current buffer.")))) 2571 2572 2573 ;; keybindings 2574 (defvar lsp--binding-descriptions nil 2575 "List of key binding/short description pair.") 2576 2577 (defmacro lsp-define-conditional-key (keymap key def desc cond &rest bindings) 2578 "In KEYMAP, define key sequence KEY as DEF conditionally. 2579 This is like `define-key', except the definition disappears 2580 whenever COND evaluates to nil. 2581 DESC is the short-description for the binding. 2582 BINDINGS is a list of (key def desc cond)." 2583 (declare (indent defun) 2584 (debug (form form form form form &rest sexp))) 2585 (->> (cl-list* key def desc cond bindings) 2586 (-partition 4) 2587 (-mapcat (-lambda ((key def desc cond)) 2588 `((define-key ,keymap ,key 2589 '(menu-item 2590 ,(format "maybe-%s" def) 2591 ,def 2592 :filter 2593 (lambda (item) 2594 (when (with-current-buffer (or (when (buffer-live-p lsp--describe-buffer) 2595 lsp--describe-buffer) 2596 (current-buffer)) 2597 ,cond) 2598 item)))) 2599 (when (stringp ,key) 2600 (setq lsp--binding-descriptions 2601 (append lsp--binding-descriptions '(,key ,desc))))))) 2602 macroexp-progn)) 2603 2604 (defvar lsp--describe-buffer nil) 2605 2606 (defun lsp-describe-buffer-bindings-advice (fn buffer &optional prefix menus) 2607 (let ((lsp--describe-buffer buffer)) 2608 (funcall fn buffer prefix menus))) 2609 2610 (advice-add 'describe-buffer-bindings 2611 :around 2612 #'lsp-describe-buffer-bindings-advice) 2613 2614 (defun lsp--prepend-prefix (mappings) 2615 (->> mappings 2616 (-partition 2) 2617 (-mapcat (-lambda ((key description)) 2618 (list (concat lsp-keymap-prefix " " key) 2619 description))))) 2620 2621 (defvar lsp-command-map 2622 (-doto (make-sparse-keymap) 2623 (lsp-define-conditional-key 2624 ;; workspaces 2625 "wD" lsp-disconnect "disconnect" (lsp-workspaces) 2626 "wd" lsp-describe-session "describe session" t 2627 "wq" lsp-workspace-shutdown "shutdown server" (lsp-workspaces) 2628 "wr" lsp-workspace-restart "restart server" (lsp-workspaces) 2629 "ws" lsp "start server" t 2630 2631 ;; formatting 2632 "==" lsp-format-buffer "format buffer" (or (lsp-feature? "textDocument/rangeFormatting") 2633 (lsp-feature? "textDocument/formatting")) 2634 "=r" lsp-format-region "format region" (lsp-feature? "textDocument/rangeFormatting") 2635 2636 ;; folders 2637 "Fa" lsp-workspace-folders-add "add folder" t 2638 "Fb" lsp-workspace-blocklist-remove "un-blocklist folder" t 2639 "Fr" lsp-workspace-folders-remove "remove folder" t 2640 2641 ;; toggles 2642 "TD" lsp-modeline-diagnostics-mode "toggle modeline diagnostics" (lsp-feature? 2643 "textDocument/publishDiagnostics") 2644 "TL" lsp-toggle-trace-io "toggle log io" t 2645 "TS" lsp-ui-sideline-mode "toggle sideline" (featurep 'lsp-ui-sideline) 2646 "TT" lsp-treemacs-sync-mode "toggle treemacs integration" (featurep 'lsp-treemacs) 2647 "Ta" lsp-modeline-code-actions-mode "toggle modeline code actions" (lsp-feature? 2648 "textDocument/codeAction") 2649 "Tb" lsp-headerline-breadcrumb-mode "toggle breadcrumb" (lsp-feature? 2650 "textDocument/documentSymbol") 2651 "Td" lsp-ui-doc-mode "toggle documentation popup" (featurep 'lsp-ui-doc) 2652 "Tf" lsp-toggle-on-type-formatting "toggle on type formatting" (lsp-feature? 2653 "textDocument/onTypeFormatting") 2654 "Th" lsp-toggle-symbol-highlight "toggle highlighting" (lsp-feature? "textDocument/documentHighlight") 2655 "Tl" lsp-lens-mode "toggle lenses" (lsp-feature? "textDocument/codeLens") 2656 "Ts" lsp-toggle-signature-auto-activate "toggle signature" (lsp-feature? "textDocument/signatureHelp") 2657 2658 ;; goto 2659 "ga" xref-find-apropos "find symbol in workspace" (lsp-feature? "workspace/symbol") 2660 "gd" lsp-find-declaration "find declarations" (lsp-feature? "textDocument/declaration") 2661 "ge" lsp-treemacs-errors-list "show errors" (fboundp 'lsp-treemacs-errors-list) 2662 "gg" lsp-find-definition "find definitions" (lsp-feature? "textDocument/definition") 2663 "gh" lsp-treemacs-call-hierarchy "call hierarchy" (and (lsp-feature? "callHierarchy/incomingCalls") 2664 (fboundp 'lsp-treemacs-call-hierarchy)) 2665 "gi" lsp-find-implementation "find implementations" (lsp-feature? "textDocument/implementation") 2666 "gr" lsp-find-references "find references" (lsp-feature? "textDocument/references") 2667 "gt" lsp-find-type-definition "find type definition" (lsp-feature? "textDocument/typeDefinition") 2668 2669 ;; help 2670 "hg" lsp-ui-doc-glance "glance symbol" (and (featurep 'lsp-ui-doc) 2671 (lsp-feature? "textDocument/hover")) 2672 "hh" lsp-describe-thing-at-point "describe symbol at point" (lsp-feature? "textDocument/hover") 2673 "hs" lsp-signature-activate "signature help" (lsp-feature? "textDocument/signatureHelp") 2674 2675 ;; refactoring 2676 "ro" lsp-organize-imports "organize imports" (lsp-feature? "textDocument/codeAction") 2677 "rr" lsp-rename "rename" (lsp-feature? "textDocument/rename") 2678 2679 ;; actions 2680 "aa" lsp-execute-code-action "code actions" (lsp-feature? "textDocument/codeAction") 2681 "ah" lsp-document-highlight "highlight symbol" (lsp-feature? "textDocument/documentHighlight") 2682 "al" lsp-avy-lens "lens" (and (bound-and-true-p lsp-lens-mode) (featurep 'avy)) 2683 2684 ;; peeks 2685 "Gg" lsp-ui-peek-find-definitions "peek definitions" (and (lsp-feature? "textDocument/definition") 2686 (fboundp 'lsp-ui-peek-find-definitions)) 2687 "Gi" lsp-ui-peek-find-implementation "peek implementations" (and 2688 (fboundp 'lsp-ui-peek-find-implementation) 2689 (lsp-feature? "textDocument/implementation")) 2690 "Gr" lsp-ui-peek-find-references "peek references" (and (fboundp 'lsp-ui-peek-find-references) 2691 (lsp-feature? "textDocument/references")) 2692 "Gs" lsp-ui-peek-find-workspace-symbol "peek workspace symbol" (and (fboundp 2693 'lsp-ui-peek-find-workspace-symbol) 2694 (lsp-feature? "workspace/symbol"))))) 2695 2696 2697 ;; which-key integration 2698 2699 (declare-function which-key-add-major-mode-key-based-replacements "ext:which-key") 2700 (declare-function which-key-add-key-based-replacements "ext:which-key") 2701 2702 (defun lsp-enable-which-key-integration (&optional all-modes) 2703 "Adds descriptions for `lsp-mode-map' to `which-key-mode' for the current 2704 active `major-mode', or for all major modes when ALL-MODES is t." 2705 (cl-flet ((which-key-fn (if all-modes 2706 'which-key-add-key-based-replacements 2707 (apply-partially 'which-key-add-major-mode-key-based-replacements major-mode)))) 2708 (apply 2709 #'which-key-fn 2710 (lsp--prepend-prefix 2711 (cl-list* 2712 "" "lsp" 2713 "w" "workspaces" 2714 "F" "folders" 2715 "=" "formatting" 2716 "T" "toggle" 2717 "g" "goto" 2718 "h" "help" 2719 "r" "refactor" 2720 "a" "code actions" 2721 "G" "peek" 2722 lsp--binding-descriptions))))) 2723 2724 2725 ;; Globbing syntax 2726 2727 ;; We port VSCode's glob-to-regexp code 2728 ;; (https://github.com/Microsoft/vscode/blob/466da1c9013c624140f6d1473b23a870abc82d44/src/vs/base/common/glob.ts) 2729 ;; since the LSP globbing syntax seems to be the same as that of 2730 ;; VSCode. 2731 2732 (defconst lsp-globstar "**" 2733 "Globstar pattern.") 2734 2735 (defconst lsp-glob-split ?/ 2736 "The character by which we split path components in a glob 2737 pattern.") 2738 2739 (defconst lsp-path-regexp "[/\\\\]" 2740 "Forward or backslash to be used as a path separator in 2741 computed regexps.") 2742 2743 (defconst lsp-non-path-regexp "[^/\\\\]" 2744 "A regexp matching anything other than a slash.") 2745 2746 (defconst lsp-globstar-regexp 2747 (format "\\(?:%s\\|%s+%s\\|%s%s+\\)*?" 2748 lsp-path-regexp 2749 lsp-non-path-regexp lsp-path-regexp 2750 lsp-path-regexp lsp-non-path-regexp) 2751 "Globstar in regexp form.") 2752 2753 (defun lsp-split-glob-pattern (pattern split-char) 2754 "Split PATTERN at SPLIT-CHAR while respecting braces and brackets." 2755 (when pattern 2756 (let ((segments nil) 2757 (in-braces nil) 2758 (in-brackets nil) 2759 (current-segment "")) 2760 (dolist (char (string-to-list pattern)) 2761 (cl-block 'exit-point 2762 (if (eq char split-char) 2763 (when (and (null in-braces) 2764 (null in-brackets)) 2765 (push current-segment segments) 2766 (setq current-segment "") 2767 (cl-return-from 'exit-point)) 2768 (pcase char 2769 (?{ 2770 (setq in-braces t)) 2771 (?} 2772 (setq in-braces nil)) 2773 (?\[ 2774 (setq in-brackets t)) 2775 (?\] 2776 (setq in-brackets nil)))) 2777 (setq current-segment (concat current-segment 2778 (char-to-string char))))) 2779 (unless (string-empty-p current-segment) 2780 (push current-segment segments)) 2781 (nreverse segments)))) 2782 2783 (defun lsp--glob-to-regexp (pattern) 2784 "Helper function to convert a PATTERN from LSP's glob syntax to 2785 an Elisp regexp." 2786 (if (string-empty-p pattern) 2787 "" 2788 (let ((current-regexp "") 2789 (glob-segments (lsp-split-glob-pattern pattern lsp-glob-split))) 2790 (if (-all? (lambda (segment) (eq segment lsp-globstar)) 2791 glob-segments) 2792 ".*" 2793 (let ((prev-segment-was-globstar nil)) 2794 (seq-do-indexed 2795 (lambda (segment index) 2796 (if (string-equal segment lsp-globstar) 2797 (unless prev-segment-was-globstar 2798 (setq current-regexp (concat current-regexp 2799 lsp-globstar-regexp)) 2800 (setq prev-segment-was-globstar t)) 2801 (let ((in-braces nil) 2802 (brace-val "") 2803 (in-brackets nil) 2804 (bracket-val "")) 2805 (dolist (char (string-to-list segment)) 2806 (cond 2807 ((and (not (char-equal char ?\})) 2808 in-braces) 2809 (setq brace-val (concat brace-val 2810 (char-to-string char)))) 2811 ((and in-brackets 2812 (or (not (char-equal char ?\])) 2813 (string-empty-p bracket-val))) 2814 (let ((curr (cond 2815 ((char-equal char ?-) 2816 "-") 2817 ;; NOTE: ?\^ and ?^ are different characters 2818 ((and (memq char '(?^ ?!)) 2819 (string-empty-p bracket-val)) 2820 "^") 2821 ((char-equal char lsp-glob-split) 2822 "") 2823 (t 2824 (regexp-quote (char-to-string char)))))) 2825 (setq bracket-val (concat bracket-val curr)))) 2826 (t 2827 (cl-case char 2828 (?{ 2829 (setq in-braces t)) 2830 (?\[ 2831 (setq in-brackets t)) 2832 (?} 2833 (let* ((choices (lsp-split-glob-pattern brace-val ?\,)) 2834 (brace-regexp (concat "\\(?:" 2835 (mapconcat #'lsp--glob-to-regexp choices "\\|") 2836 "\\)"))) 2837 (setq current-regexp (concat current-regexp 2838 brace-regexp)) 2839 (setq in-braces nil) 2840 (setq brace-val ""))) 2841 (?\] 2842 (setq current-regexp 2843 (concat current-regexp 2844 "[" bracket-val "]")) 2845 (setq in-brackets nil) 2846 (setq bracket-val "")) 2847 (?? 2848 (setq current-regexp 2849 (concat current-regexp 2850 lsp-non-path-regexp))) 2851 (?* 2852 (setq current-regexp 2853 (concat current-regexp 2854 lsp-non-path-regexp "*?"))) 2855 (t 2856 (setq current-regexp 2857 (concat current-regexp 2858 (regexp-quote (char-to-string char))))))))) 2859 (when (and (< index (1- (length glob-segments))) 2860 (or (not (string-equal (nth (1+ index) glob-segments) 2861 lsp-globstar)) 2862 (< (+ index 2) 2863 (length glob-segments)))) 2864 (setq current-regexp 2865 (concat current-regexp 2866 lsp-path-regexp))) 2867 (setq prev-segment-was-globstar nil)))) 2868 glob-segments) 2869 current-regexp))))) 2870 2871 ;; See https://github.com/emacs-lsp/lsp-mode/issues/2365 2872 (defun lsp-glob-unbrace-at-top-level (glob-pattern) 2873 "If GLOB-PATTERN does not start with a brace, return a singleton list 2874 containing GLOB-PATTERN. 2875 2876 If GLOB-PATTERN does start with a brace, return a list of the 2877 comma-separated globs within the top-level braces." 2878 (if (not (string-prefix-p "{" glob-pattern)) 2879 (list glob-pattern) 2880 (lsp-split-glob-pattern (substring glob-pattern 1 -1) ?\,))) 2881 2882 (defun lsp-glob-convert-to-wrapped-regexp (glob-pattern) 2883 "Convert GLOB-PATTERN to a regexp wrapped with the beginning- 2884 and end-of-string meta-characters." 2885 (concat "\\`" (lsp--glob-to-regexp (string-trim glob-pattern)) "\\'")) 2886 2887 (defun lsp-glob-to-regexps (glob-pattern) 2888 "Convert a GLOB-PATTERN to a list of Elisp regexps." 2889 (when-let* 2890 ((glob-pattern (cond ((hash-table-p glob-pattern) 2891 (ht-get glob-pattern "pattern")) 2892 ((stringp glob-pattern) glob-pattern) 2893 (t (error "Unknown glob-pattern type: %s" glob-pattern)))) 2894 (trimmed-pattern (string-trim glob-pattern)) 2895 (top-level-unbraced-patterns (lsp-glob-unbrace-at-top-level trimmed-pattern))) 2896 (seq-map #'lsp-glob-convert-to-wrapped-regexp 2897 top-level-unbraced-patterns))) 2898 2899 2900 2901 (defvar lsp-mode-menu) 2902 2903 (defun lsp-mouse-click (event) 2904 (interactive "e") 2905 (let* ((ec (event-start event)) 2906 (choice (x-popup-menu event lsp-mode-menu)) 2907 (action (lookup-key lsp-mode-menu (apply 'vector choice)))) 2908 2909 (select-window (posn-window ec)) 2910 2911 (unless (and (region-active-p) (eq action 'lsp-execute-code-action)) 2912 (goto-char (posn-point ec))) 2913 (run-with-idle-timer 2914 0.001 nil 2915 (lambda () 2916 (cl-labels ((check (value) (not (null value)))) 2917 (when choice 2918 (call-interactively action))))))) 2919 2920 (defvar lsp-mode-map 2921 (let ((map (make-sparse-keymap))) 2922 (define-key map (kbd "C-<down-mouse-1>") #'lsp-find-definition-mouse) 2923 (define-key map (kbd "C-<mouse-1>") #'ignore) 2924 (define-key map (kbd "<mouse-3>") #'lsp-mouse-click) 2925 (define-key map (kbd "C-S-SPC") #'lsp-signature-activate) 2926 (when lsp-keymap-prefix 2927 (define-key map (kbd lsp-keymap-prefix) lsp-command-map)) 2928 map) 2929 "Keymap for `lsp-mode'.") 2930 2931 (define-minor-mode lsp-mode "Mode for LSP interaction." 2932 :keymap lsp-mode-map 2933 :lighter 2934 (" LSP[" 2935 (lsp--buffer-workspaces 2936 (:eval (mapconcat #'lsp--workspace-print lsp--buffer-workspaces "][")) 2937 (:propertize "Disconnected" face warning)) 2938 "]") 2939 :group 'lsp-mode 2940 (when (and lsp-mode (not lsp--buffer-workspaces) (not lsp--buffer-deferred)) 2941 ;; fire up `lsp' when someone calls `lsp-mode' instead of `lsp' 2942 (lsp))) 2943 2944 (defvar lsp-mode-menu 2945 (easy-menu-create-menu 2946 nil 2947 `(["Go to definition" lsp-find-definition 2948 :active (lsp-feature? "textDocument/definition")] 2949 ["Find references" lsp-find-references 2950 :active (lsp-feature? "textDocument/references")] 2951 ["Find implementations" lsp-find-implementation 2952 :active (lsp-feature? "textDocument/implementation")] 2953 ["Find declarations" lsp-find-declaration 2954 :active (lsp-feature? "textDocument/declaration")] 2955 ["Go to type declaration" lsp-find-type-definition 2956 :active (lsp-feature? "textDocument/typeDefinition")] 2957 "--" 2958 ["Describe" lsp-describe-thing-at-point] 2959 ["Code action" lsp-execute-code-action] 2960 ["Format" lsp-format-buffer] 2961 ["Highlight references" lsp-document-highlight] 2962 ["Type Hierarchy" lsp-java-type-hierarchy 2963 :visible (lsp-can-execute-command? "java.navigate.resolveTypeHierarchy")] 2964 ["Type Hierarchy" lsp-treemacs-type-hierarchy 2965 :visible (and (not (lsp-can-execute-command? "java.navigate.resolveTypeHierarchy")) 2966 (functionp 'lsp-treemacs-type-hierarchy) 2967 (lsp-feature? "textDocument/typeHierarchy"))] 2968 ["Call Hierarchy" lsp-treemacs-call-hierarchy 2969 :visible (and (functionp 'lsp-treemacs-call-hierarchy) 2970 (lsp-feature? "textDocument/callHierarchy"))] 2971 ["Rename" lsp-rename 2972 :active (lsp-feature? "textDocument/rename")] 2973 "--" 2974 ("Session" 2975 ["View logs" lsp-workspace-show-log] 2976 ["Describe" lsp-describe-session] 2977 ["Shutdown" lsp-shutdown-workspace] 2978 ["Restart" lsp-restart-workspace]) 2979 ("Workspace Folders" 2980 ["Add" lsp-workspace-folders-add] 2981 ["Remove" lsp-workspace-folders-remove] 2982 ["Open" lsp-workspace-folders-open]) 2983 ("Toggle features" 2984 ["Lenses" lsp-lens-mode] 2985 ["Headerline breadcrumb" lsp-headerline-breadcrumb-mode] 2986 ["Modeline code actions" lsp-modeline-code-actions-mode] 2987 ["Modeline diagnostics" lsp-modeline-diagnostics-mode]) 2988 "---" 2989 ("Debug" 2990 :active (bound-and-true-p dap-ui-mode) 2991 :filter ,(lambda (_) 2992 (and (boundp 'dap-ui-menu-items) 2993 (nthcdr 3 dap-ui-menu-items)))))) 2994 "Menu for lsp-mode.") 2995 2996 (defalias 'make-lsp-client 'make-lsp--client) 2997 2998 (cl-defstruct lsp--registered-capability 2999 (id "") 3000 (method " ") 3001 (options nil)) 3002 3003 ;; A ‘lsp--workspace’ object represents exactly one language server process. 3004 (cl-defstruct lsp--workspace 3005 ;; the `ewoc' object for displaying I/O to and from the server 3006 (ewoc nil) 3007 3008 ;; ‘server-capabilities’ is a hash table of the language server capabilities. 3009 ;; It is the hash table representation of a LSP ServerCapabilities structure; 3010 ;; cf. https://microsoft.github.io/language-server-protocol/specification#initialize. 3011 (server-capabilities nil) 3012 3013 ;; ‘registered-server-capabilities’ is a list of hash tables that represent 3014 ;; dynamically-registered Registration objects. See 3015 ;; https://microsoft.github.io/language-server-protocol/specification#client_registerCapability. 3016 (registered-server-capabilities nil) 3017 3018 ;; ‘root’ is a directory name or a directory file name for the workspace 3019 ;; root. ‘lsp-mode’ passes this directory to the ‘initialize’ method of the 3020 ;; language server; see 3021 ;; https://microsoft.github.io/language-server-protocol/specification#initialize. 3022 (root nil) 3023 3024 ;; ‘client’ is the ‘lsp--client’ object associated with this workspace. 3025 (client nil) 3026 3027 ;; ‘host-root’ contains the host root info as derived from `file-remote-p'. It 3028 ;; used to derive the file path in `lsp--uri-to-path' when using tramp 3029 ;; connection. 3030 (host-root nil) 3031 3032 ;; ‘proc’ is a process object; it may represent a regular process, a pipe, or 3033 ;; a network connection. ‘lsp-mode’ communicates with ‘proc’ using the 3034 ;; language server protocol. ‘proc’ corresponds to the COMMUNICATION-PROCESS 3035 ;; element of the return value of the client’s ‘get-root’ field, which see. 3036 (proc nil) 3037 3038 ;; ‘proc’ is a process object; it must represent a regular process, not a 3039 ;; pipe or network process. It represents the actual server process that 3040 ;; corresponds to this workspace. ‘cmd-proc’ corresponds to the 3041 ;; COMMAND-PROCESS element of the return value of the client’s ‘get-root’ 3042 ;; field, which see. 3043 (cmd-proc nil) 3044 3045 ;; ‘buffers’ is a list of buffers associated with this workspace. 3046 (buffers nil) 3047 3048 ;; if semantic tokens is enabled, `semantic-tokens-faces' contains 3049 ;; one face (or nil) for each token type supported by the language server. 3050 (semantic-tokens-faces nil) 3051 3052 ;; If semantic highlighting is enabled, `semantic-tokens-modifier-faces' 3053 ;; contains one face (or nil) for each modifier type supported by the language 3054 ;; server 3055 (semantic-tokens-modifier-faces nil) 3056 3057 ;; Extra client capabilities provided by third-party packages using 3058 ;; `lsp-register-client-capabilities'. It's value is an alist of (PACKAGE-NAME 3059 ;; . CAPS), where PACKAGE-NAME is a symbol of the third-party package name, 3060 ;; and CAPS is either a plist of the client capabilities, or a function that 3061 ;; takes no argument and returns a plist of the client capabilities or nil. 3062 (extra-client-capabilities nil) 3063 3064 ;; Workspace status 3065 (status nil) 3066 3067 ;; ‘metadata’ is a generic storage for workspace specific data. It is 3068 ;; accessed via `lsp-workspace-set-metadata' and `lsp-workspace-set-metadata' 3069 (metadata (make-hash-table :test 'equal)) 3070 3071 ;; contains all the file notification watches that have been created for the 3072 ;; current workspace in format filePath->file notification handle. 3073 (watches (make-hash-table :test 'equal)) 3074 3075 ;; list of workspace folders 3076 (workspace-folders nil) 3077 3078 ;; ‘last-id’ the last request id for the current workspace. 3079 (last-id 0) 3080 3081 ;; ‘status-string’ allows extensions to specify custom status string based on 3082 ;; the Language Server specific messages. 3083 (status-string nil) 3084 3085 ;; ‘shutdown-action’ flag used to mark that workspace should not be restarted (e.g. it 3086 ;; was stopped). 3087 shutdown-action 3088 3089 ;; ‘diagnostics’ a hashmap with workspace diagnostics. 3090 (diagnostics (make-hash-table :test 'equal)) 3091 3092 ;; contains all the workDone progress tokens that have been created 3093 ;; for the current workspace. 3094 (work-done-tokens (make-hash-table :test 'equal))) 3095 3096 3097 (cl-defstruct lsp-session 3098 ;; contains the folders that are part of the current session 3099 folders 3100 ;; contains the folders that must not be imported in the current workspace. 3101 folders-blocklist 3102 ;; contains the list of folders that must be imported in a project in case of 3103 ;; multi root LSP server. 3104 (server-id->folders (make-hash-table :test 'equal)) 3105 ;; folder to list of the servers that are associated with the folder. 3106 (folder->servers (make-hash-table :test 'equal)) 3107 ;; ‘metadata’ is a generic storage for workspace specific data. It is 3108 ;; accessed via `lsp-workspace-set-metadata' and `lsp-workspace-set-metadata' 3109 (metadata (make-hash-table :test 'equal))) 3110 3111 (defun lsp-workspace-status (status-string &optional workspace) 3112 "Set current workspace status to STATUS-STRING. 3113 If WORKSPACE is not specified defaults to lsp--cur-workspace." 3114 (let ((status-string (when status-string (replace-regexp-in-string "%" "%%" status-string)))) 3115 (setf (lsp--workspace-status-string (or workspace lsp--cur-workspace)) status-string))) 3116 3117 (defun lsp-session-set-metadata (key value &optional _workspace) 3118 "Associate KEY with VALUE in the WORKSPACE metadata. 3119 If WORKSPACE is not provided current workspace will be used." 3120 (puthash key value (lsp-session-metadata (lsp-session)))) 3121 3122 (defalias 'lsp-workspace-set-metadata 'lsp-session-set-metadata) 3123 3124 (defun lsp-session-get-metadata (key &optional _workspace) 3125 "Lookup KEY in WORKSPACE metadata. 3126 If WORKSPACE is not provided current workspace will be used." 3127 (gethash key (lsp-session-metadata (lsp-session)))) 3128 3129 (defalias 'lsp-workspace-get-metadata 'lsp-session-get-metadata) 3130 3131 (defun lsp-workspace-set-work-done-token (token value workspace) 3132 "Associate TOKEN with VALUE in the WORKSPACE work-done-tokens." 3133 (puthash token value (lsp--workspace-work-done-tokens workspace))) 3134 3135 (defun lsp-workspace-get-work-done-token (token workspace) 3136 "Lookup TOKEN in the WORKSPACE work-done-tokens." 3137 (gethash token (lsp--workspace-work-done-tokens workspace))) 3138 3139 (defun lsp-workspace-rem-work-done-token (token workspace) 3140 "Remove TOKEN from the WORKSPACE work-done-tokens." 3141 (remhash token (lsp--workspace-work-done-tokens workspace))) 3142 3143 3144 (defun lsp--make-notification (method &optional params) 3145 "Create notification body for method METHOD and parameters PARAMS." 3146 (list :jsonrpc "2.0" :method method :params params)) 3147 3148 (defalias 'lsp--make-request 'lsp--make-notification) 3149 (defalias 'lsp-make-request 'lsp--make-notification) 3150 3151 (defun lsp--make-response (id result) 3152 "Create response for REQUEST with RESULT." 3153 `(:jsonrpc "2.0" :id ,id :result ,result)) 3154 3155 (defun lsp-make-notification (method &optional params) 3156 "Create notification body for method METHOD and parameters PARAMS." 3157 (lsp--make-notification method params)) 3158 3159 (defmacro lsp--json-serialize (params) 3160 (if (progn 3161 (require 'json) 3162 (fboundp 'json-serialize)) 3163 `(json-serialize ,params 3164 :null-object nil 3165 :false-object :json-false) 3166 `(let ((json-false :json-false)) 3167 (json-encode ,params)))) 3168 3169 (defun lsp--make-message (params) 3170 "Create a LSP message from PARAMS, after encoding it to a JSON string." 3171 (let ((body (lsp--json-serialize params))) 3172 (concat "Content-Length: " 3173 (number-to-string (1+ (string-bytes body))) 3174 "\r\n\r\n" 3175 body 3176 "\n"))) 3177 3178 (cl-defstruct lsp--log-entry timestamp process-time type method id body) 3179 3180 (defun lsp--make-log-entry (method id body type &optional process-time) 3181 "Create an outgoing log object from BODY with method METHOD and id ID. 3182 If ID is non-nil, then the body is assumed to be a notification. 3183 TYPE can either be `incoming' or `outgoing'" 3184 (cl-assert (memq type '(incoming-req outgoing-req incoming-notif 3185 outgoing-notif incoming-resp 3186 outgoing-resp))) 3187 (make-lsp--log-entry 3188 :timestamp (format-time-string "%I:%M:%S %p") 3189 :process-time process-time 3190 :method method 3191 :id id 3192 :type type 3193 :body body)) 3194 3195 (defun lsp--log-font-lock-json (body) 3196 "Font lock JSON BODY." 3197 (with-temp-buffer 3198 (insert body) 3199 ;; We set the temp buffer file-name extension to .json and call `set-auto-mode' 3200 ;; so the users configured json mode is used which could be 3201 ;; `json-mode', `json-ts-mode', `jsonian-mode', etc. 3202 (let ((buffer-file-name "lsp-log.json")) 3203 (delay-mode-hooks 3204 (set-auto-mode) 3205 (if (fboundp 'font-lock-ensure) 3206 (font-lock-ensure) 3207 (with-no-warnings 3208 (font-lock-fontify-buffer))))) 3209 (buffer-string))) 3210 3211 (defun lsp--log-entry-pp (entry) 3212 (cl-assert (lsp--log-entry-p entry)) 3213 (pcase-let (((cl-struct lsp--log-entry timestamp method id type process-time 3214 body) 3215 entry) 3216 (json-false :json-false) 3217 (json-encoding-pretty-print t) 3218 (str nil)) 3219 (setq str 3220 (concat (format "[Trace - %s] " timestamp) 3221 (pcase type 3222 ('incoming-req (format "Received request '%s - (%s)." method id)) 3223 ('outgoing-req (format "Sending request '%s - (%s)'." method id)) 3224 3225 ('incoming-notif (format "Received notification '%s'." method)) 3226 ('outgoing-notif (format "Sending notification '%s'." method)) 3227 3228 ('incoming-resp (format "Received response '%s - (%s)' in %dms." 3229 method id process-time)) 3230 ('outgoing-resp 3231 (format 3232 "Sending response '%s - (%s)'. Processing request took %dms" 3233 method id process-time))) 3234 "\n" 3235 (if (memq type '(incoming-resp ougoing-resp)) 3236 "Result: " 3237 "Params: ") 3238 (lsp--log-font-lock-json (json-encode body)) 3239 "\n\n\n")) 3240 (setq str (propertize str 'mouse-face 'highlight 'read-only t)) 3241 (insert str))) 3242 3243 (defvar-local lsp--log-io-ewoc nil) 3244 3245 (defun lsp--get-create-io-ewoc (workspace) 3246 (if (and (lsp--workspace-ewoc workspace) 3247 (buffer-live-p (ewoc-buffer (lsp--workspace-ewoc workspace)))) 3248 (lsp--workspace-ewoc workspace) 3249 (with-current-buffer (lsp--get-log-buffer-create workspace) 3250 (unless (eq 'lsp-log-io-mode major-mode) (lsp-log-io-mode)) 3251 (setq-local window-point-insertion-type t) 3252 (setq lsp--log-io-ewoc (ewoc-create #'lsp--log-entry-pp nil nil t)) 3253 (setf (lsp--workspace-ewoc workspace) lsp--log-io-ewoc)) 3254 (lsp--workspace-ewoc workspace))) 3255 3256 (defun lsp--ewoc-count (ewoc) 3257 (let* ((count 0) 3258 (count-fn (lambda (_) (setq count (1+ count))))) 3259 (ewoc-map count-fn ewoc) 3260 count)) 3261 3262 (defun lsp--log-entry-new (entry workspace) 3263 (let* ((ewoc (lsp--get-create-io-ewoc workspace)) 3264 (count (and (not (eq lsp-io-messages-max t)) (lsp--ewoc-count ewoc))) 3265 (node (if (or (eq lsp-io-messages-max t) 3266 (>= lsp-io-messages-max count)) 3267 nil 3268 (ewoc-nth ewoc (1- lsp-io-messages-max)))) 3269 (prev nil) 3270 (inhibit-read-only t)) 3271 (while node 3272 (setq prev (ewoc-prev ewoc node)) 3273 (ewoc-delete ewoc node) 3274 (setq node prev)) 3275 (ewoc-enter-last ewoc entry))) 3276 3277 (defun lsp--send-notification (body) 3278 "Send BODY as a notification to the language server." 3279 (lsp-foreach-workspace 3280 (when (lsp--log-io-p (plist-get body :method)) 3281 (lsp--log-entry-new (lsp--make-log-entry 3282 (plist-get body :method) 3283 nil (plist-get body :params) 'outgoing-notif) 3284 lsp--cur-workspace)) 3285 (lsp--send-no-wait body 3286 (lsp--workspace-proc lsp--cur-workspace)))) 3287 3288 (defalias 'lsp-send-notification 'lsp--send-notification) 3289 3290 (defun lsp-notify (method params) 3291 "Send notification METHOD with PARAMS." 3292 (lsp--send-notification (lsp--make-notification method params))) 3293 3294 (defun lsp--cur-workspace-check () 3295 "Check whether buffer lsp workspace(s) are set." 3296 (cl-assert (lsp-workspaces) nil 3297 "No language server(s) is associated with this buffer.")) 3298 3299 (defun lsp--send-request (body &optional no-wait no-merge) 3300 "Send BODY as a request to the language server, get the response. 3301 If NO-WAIT is non-nil, don't synchronously wait for a response. 3302 If NO-MERGE is non-nil, don't merge the results but return an 3303 alist mapping workspace->result." 3304 (lsp-request (plist-get body :method) 3305 (plist-get body :params) 3306 :no-wait no-wait 3307 :no-merge no-merge)) 3308 3309 (defalias 'lsp-send-request 'lsp--send-request 3310 "Send BODY as a request to the language server and return the response 3311 synchronously. 3312 \n(fn BODY)") 3313 3314 (cl-defun lsp-request (method params &key no-wait no-merge) 3315 "Send request METHOD with PARAMS. 3316 If NO-MERGE is non-nil, don't merge the results but return alist 3317 workspace->result. 3318 If NO-WAIT is non-nil send the request as notification." 3319 (if no-wait 3320 (lsp-notify method params) 3321 (let* ((send-time (float-time)) 3322 ;; max time by which we must get a response 3323 (expected-time 3324 (and 3325 lsp-response-timeout 3326 (+ send-time lsp-response-timeout))) 3327 resp-result resp-error done?) 3328 (unwind-protect 3329 (progn 3330 (lsp-request-async method params 3331 (lambda (res) (setf resp-result (or res :finished)) (throw 'lsp-done '_)) 3332 :error-handler (lambda (err) (setf resp-error err) (throw 'lsp-done '_)) 3333 :no-merge no-merge 3334 :mode 'detached 3335 :cancel-token :sync-request) 3336 (while (not (or resp-error resp-result)) 3337 (if (functionp 'json-rpc-connection) 3338 (catch 'lsp-done (sit-for 0.01)) 3339 (catch 'lsp-done 3340 (accept-process-output 3341 nil 3342 (if expected-time (- expected-time send-time) 1)))) 3343 (setq send-time (float-time)) 3344 (when (and expected-time (< expected-time send-time)) 3345 (error "Timeout while waiting for response. Method: %s" method))) 3346 (setq done? t) 3347 (cond 3348 ((eq resp-result :finished) nil) 3349 (resp-result resp-result) 3350 ((lsp-json-error? resp-error) (error (lsp:json-error-message resp-error))) 3351 ((lsp-json-error? (cl-first resp-error)) 3352 (error (lsp:json-error-message (cl-first resp-error)))))) 3353 (unless done? 3354 (lsp-cancel-request-by-token :sync-request)))))) 3355 3356 (cl-defun lsp-request-while-no-input (method params) 3357 "Send request METHOD with PARAMS and waits until there is no input. 3358 Return same value as `lsp--while-no-input' and respecting `non-essential'." 3359 (if (or non-essential (not lsp-request-while-no-input-may-block)) 3360 (let* ((send-time (float-time)) 3361 ;; max time by which we must get a response 3362 (expected-time 3363 (and 3364 lsp-response-timeout 3365 (+ send-time lsp-response-timeout))) 3366 resp-result resp-error done?) 3367 (unwind-protect 3368 (progn 3369 (lsp-request-async method params 3370 (lambda (res) (setf resp-result (or res :finished)) (throw 'lsp-done '_)) 3371 :error-handler (lambda (err) (setf resp-error err) (throw 'lsp-done '_)) 3372 :mode 'detached 3373 :cancel-token :sync-request) 3374 (while (not (or resp-error resp-result (input-pending-p))) 3375 (catch 'lsp-done 3376 (sit-for 3377 (if expected-time (- expected-time send-time) 1))) 3378 (setq send-time (float-time)) 3379 (when (and expected-time (< expected-time send-time)) 3380 (error "Timeout while waiting for response. Method: %s" method))) 3381 (setq done? (or resp-error resp-result)) 3382 (cond 3383 ((eq resp-result :finished) nil) 3384 (resp-result resp-result) 3385 ((lsp-json-error? resp-error) (error (lsp:json-error-message resp-error))) 3386 ((lsp-json-error? (cl-first resp-error)) 3387 (error (lsp:json-error-message (cl-first resp-error)))))) 3388 (unless done? 3389 (lsp-cancel-request-by-token :sync-request)) 3390 (when (and (input-pending-p) lsp--throw-on-input) 3391 (throw 'input :interrupted)))) 3392 (lsp-request method params))) 3393 3394 (defvar lsp--cancelable-requests (ht)) 3395 3396 (cl-defun lsp-request-async (method params callback 3397 &key mode error-handler cancel-handler no-merge cancel-token) 3398 "Send METHOD with PARAMS as a request to the language server. 3399 Call CALLBACK with the response received from the server 3400 asynchronously. 3401 MODE determines when the callback will be called depending on the 3402 condition of the original buffer. It could be: 3403 - `detached' which means that the callback will be executed no 3404 matter what has happened to the buffer. 3405 - `alive' - the callback will be executed only if the buffer from 3406 which the call was executed is still alive. 3407 - `current' the callback will be executed only if the original buffer 3408 is still selected. 3409 - `tick' - the callback will be executed only if the buffer was not modified. 3410 - `unchanged' - the callback will be executed only if the buffer hasn't 3411 changed and if the buffer is not modified. 3412 3413 ERROR-HANDLER will be called in case the request has failed. 3414 CANCEL-HANDLER will be called in case the request is being canceled. 3415 If NO-MERGE is non-nil, don't merge the results but return alist 3416 workspace->result. 3417 CANCEL-TOKEN is the token that can be used to cancel request." 3418 (lsp--send-request-async `(:jsonrpc "2.0" :method ,method :params ,params) 3419 callback mode error-handler cancel-handler no-merge cancel-token)) 3420 3421 (defun lsp--create-request-cancel (id workspaces hook buf method cancel-callback) 3422 (lambda (&rest _) 3423 (unless (and (equal 'post-command-hook hook) 3424 (equal (current-buffer) buf)) 3425 (lsp--request-cleanup-hooks id) 3426 (with-lsp-workspaces workspaces 3427 (lsp--cancel-request id) 3428 (when cancel-callback (funcall cancel-callback))) 3429 (lsp-log "Cancelling %s(%s) in hook %s" method id hook)))) 3430 3431 (defun lsp--create-async-callback 3432 (callback method no-merge workspaces) 3433 "Create async handler expecting COUNT results, merge them and call CALLBACK. 3434 MODE determines when the callback will be called depending on the 3435 condition of the original buffer. METHOD is the invoked method. 3436 If NO-MERGE is non-nil, don't merge the results but return alist 3437 workspace->result. ID is the request id." 3438 (let (results errors) 3439 (lambda (result) 3440 (push (cons lsp--cur-workspace result) 3441 (if (eq result :error) errors results)) 3442 (when (and (not (eq (length errors) (length workspaces))) 3443 (eq (+ (length errors) (length results)) (length workspaces))) 3444 (funcall callback 3445 (if no-merge 3446 results 3447 (lsp--merge-results (-map #'cl-rest results) method))))))) 3448 3449 (defcustom lsp-default-create-error-handler-fn nil 3450 "Default error handler customization. 3451 Handler should give METHOD as argument and return function of one argument 3452 ERROR." 3453 :type 'function 3454 :group 'lsp-mode 3455 :package-version '(lsp-mode . "9.0.0")) 3456 3457 (defun lsp--create-default-error-handler (method) 3458 "Default error handler. 3459 METHOD is the executed method." 3460 (if lsp-default-create-error-handler-fn 3461 (funcall lsp-default-create-error-handler-fn method) 3462 (lambda (error) 3463 (lsp--warn "%s" (or (lsp--error-string error) 3464 (format "%s Request has failed" method)))))) 3465 3466 (defvar lsp--request-cleanup-hooks (ht)) 3467 3468 (defun lsp--request-cleanup-hooks (request-id) 3469 (when-let ((cleanup-function (gethash request-id lsp--request-cleanup-hooks))) 3470 (funcall cleanup-function) 3471 (remhash request-id lsp--request-cleanup-hooks))) 3472 3473 (defun lsp-cancel-request-by-token (cancel-token) 3474 "Cancel request using CANCEL-TOKEN." 3475 (-when-let ((request-id . workspaces) (gethash cancel-token lsp--cancelable-requests)) 3476 (with-lsp-workspaces workspaces 3477 (lsp--cancel-request request-id)) 3478 (remhash cancel-token lsp--cancelable-requests) 3479 (lsp--request-cleanup-hooks request-id))) 3480 3481 (defun lsp--send-request-async (body callback 3482 &optional mode error-callback cancel-callback 3483 no-merge cancel-token) 3484 "Send BODY as a request to the language server. 3485 Call CALLBACK with the response received from the server 3486 asynchronously. 3487 MODE determines when the callback will be called depending on the 3488 condition of the original buffer. It could be: 3489 - `detached' which means that the callback will be executed no 3490 matter what has happened to the buffer. 3491 - `alive' - the callback will be executed only if the buffer from 3492 which the call was executed is still alive. 3493 - `current' the callback will be executed only if the original buffer 3494 is still selected. 3495 - `tick' - the callback will be executed only if the buffer was not modified. 3496 - `unchanged' - the callback will be executed only if the buffer hasn't 3497 changed and if the buffer is not modified. 3498 3499 ERROR-CALLBACK will be called in case the request has failed. 3500 CANCEL-CALLBACK will be called in case the request is being canceled. 3501 If NO-MERGE is non-nil, don't merge the results but return alist 3502 workspace->result. 3503 CANCEL-TOKEN is the token that can be used to cancel request." 3504 (when cancel-token 3505 (lsp-cancel-request-by-token cancel-token)) 3506 3507 (if-let ((target-workspaces (lsp--find-workspaces-for body))) 3508 (let* ((start-time (current-time)) 3509 (method (plist-get body :method)) 3510 (id (cl-incf lsp-last-id)) 3511 (buf (current-buffer)) 3512 (cancel-callback (when cancel-callback 3513 (pcase mode 3514 ((or 'alive 'tick 'unchanged) 3515 (lambda () 3516 (with-current-buffer buf 3517 (funcall cancel-callback)))) 3518 (_ cancel-callback)))) 3519 ;; calculate what are the (hook . local) pairs which will cancel 3520 ;; the request 3521 (hooks (pcase mode 3522 ('alive '((kill-buffer-hook . t))) 3523 ('tick '((kill-buffer-hook . t) (after-change-functions . t))) 3524 ('unchanged '((after-change-functions . t) (post-command-hook . nil))) 3525 ('current '((post-command-hook . nil))))) 3526 ;; note: lambdas in emacs can be compared but we should make sure 3527 ;; that all of the captured arguments are the same - in our case 3528 ;; `lsp--create-request-cancel' will return the same lambda when 3529 ;; called with the same params. 3530 (cleanup-hooks 3531 (lambda () (mapc 3532 (-lambda ((hook . local)) 3533 (if local 3534 (when (buffer-live-p buf) 3535 (with-current-buffer buf 3536 (remove-hook hook 3537 (lsp--create-request-cancel 3538 id target-workspaces hook buf method cancel-callback) 3539 t))) 3540 (remove-hook hook (lsp--create-request-cancel 3541 id target-workspaces hook buf method cancel-callback)))) 3542 hooks) 3543 (remhash cancel-token lsp--cancelable-requests))) 3544 (callback (pcase mode 3545 ((or 'alive 'tick 'unchanged) (lambda (&rest args) 3546 (with-current-buffer buf 3547 (apply callback args)))) 3548 (_ callback))) 3549 (callback (lsp--create-async-callback callback 3550 method 3551 no-merge 3552 target-workspaces)) 3553 (callback (lambda (result) 3554 (lsp--request-cleanup-hooks id) 3555 (funcall callback result))) 3556 (error-callback (lsp--create-async-callback 3557 (or error-callback 3558 (lsp--create-default-error-handler method)) 3559 method 3560 nil 3561 target-workspaces)) 3562 (error-callback (lambda (error) 3563 (funcall callback :error) 3564 (lsp--request-cleanup-hooks id) 3565 (funcall error-callback error))) 3566 (body (plist-put body :id id))) 3567 3568 ;; cancel request in any of the hooks 3569 (mapc (-lambda ((hook . local)) 3570 (add-hook hook 3571 (lsp--create-request-cancel 3572 id target-workspaces hook buf method cancel-callback) 3573 nil local)) 3574 hooks) 3575 (puthash id cleanup-hooks lsp--request-cleanup-hooks) 3576 3577 (setq lsp--last-active-workspaces target-workspaces) 3578 3579 (when cancel-token 3580 (puthash cancel-token (cons id target-workspaces) lsp--cancelable-requests)) 3581 3582 (seq-doseq (workspace target-workspaces) 3583 (when (lsp--log-io-p method) 3584 (lsp--log-entry-new (lsp--make-log-entry method id 3585 (plist-get body :params) 3586 'outgoing-req) 3587 workspace)) 3588 (puthash id 3589 (list callback error-callback method start-time (current-time)) 3590 (-> workspace 3591 (lsp--workspace-client) 3592 (lsp--client-response-handlers))) 3593 (lsp--send-no-wait body (lsp--workspace-proc workspace))) 3594 body) 3595 (error "The connected server(s) does not support method %s. 3596 To find out what capabilities support your server use `M-x lsp-describe-session' 3597 and expand the capabilities section" 3598 (plist-get body :method)))) 3599 3600 ;; deprecated, use lsp-request-async. 3601 (defalias 'lsp-send-request-async 'lsp--send-request-async) 3602 (make-obsolete 'lsp-send-request-async 'lsp-request-async "lsp-mode 7.0.1") 3603 3604 ;; Clean up the entire state of lsp mode when Emacs is killed, to get rid of any 3605 ;; pending language servers. 3606 (add-hook 'kill-emacs-hook #'lsp--global-teardown) 3607 3608 (defun lsp--global-teardown () 3609 "Unload working workspaces." 3610 (lsp-foreach-workspace (lsp--shutdown-workspace))) 3611 3612 (defun lsp--shutdown-workspace (&optional restart) 3613 "Shut down the language server process for ‘lsp--cur-workspace’." 3614 (with-demoted-errors "LSP error: %S" 3615 (let ((lsp-response-timeout 0.5)) 3616 (condition-case err 3617 (lsp-request "shutdown" nil) 3618 (error (lsp--error "%s" err)))) 3619 (lsp-notify "exit" nil)) 3620 (setf (lsp--workspace-shutdown-action lsp--cur-workspace) (or (and restart 'restart) 'shutdown)) 3621 (lsp--uninitialize-workspace)) 3622 3623 (defcustom lsp-inlay-hint-enable nil 3624 "If non-nil it will enable inlay hints." 3625 :type 'boolean 3626 :group 'lsp-mode 3627 :package-version '(lsp-mode . "9.0.0")) 3628 3629 (defun lsp--uninitialize-workspace () 3630 "Cleanup buffer state. 3631 When a workspace is shut down, by request or from just 3632 disappearing, unset all the variables related to it." 3633 (-let [(&lsp-wks 'cmd-proc 'buffers) lsp--cur-workspace] 3634 (lsp-process-kill cmd-proc) 3635 (mapc (lambda (buf) 3636 (when (lsp-buffer-live-p buf) 3637 (lsp-with-current-buffer buf 3638 (lsp-managed-mode -1)))) 3639 buffers) 3640 (lsp-diagnostics--workspace-cleanup lsp--cur-workspace))) 3641 3642 (defun lsp--client-capabilities (&optional custom-capabilities) 3643 "Return the client capabilities appending CUSTOM-CAPABILITIES." 3644 (append 3645 `((general . ((positionEncodings . ["utf-32", "utf-16"]))) 3646 (workspace . ((workspaceEdit . ((documentChanges . t) 3647 (resourceOperations . ["create" "rename" "delete"]))) 3648 (applyEdit . t) 3649 (symbol . ((symbolKind . ((valueSet . ,(apply 'vector (number-sequence 1 26))))))) 3650 (executeCommand . ((dynamicRegistration . :json-false))) 3651 ,@(when lsp-enable-file-watchers '((didChangeWatchedFiles . ((dynamicRegistration . t))))) 3652 (workspaceFolders . t) 3653 (configuration . t) 3654 ,@(when lsp-semantic-tokens-enable 3655 `((semanticTokens . ((refreshSupport . ,(or (and (boundp 'lsp-semantic-tokens-honor-refresh-requests) 3656 lsp-semantic-tokens-honor-refresh-requests) 3657 :json-false)))))) 3658 ,@(when lsp-lens-enable '((codeLens . ((refreshSupport . t))))) 3659 ,@(when lsp-inlay-hint-enable '((inlayHint . ((refreshSupport . :json-false))))) 3660 (fileOperations . ((didCreate . :json-false) 3661 (willCreate . :json-false) 3662 (didRename . t) 3663 (willRename . t) 3664 (didDelete . :json-false) 3665 (willDelete . :json-false))))) 3666 (textDocument . ((declaration . ((dynamicRegistration . t) 3667 (linkSupport . t))) 3668 (definition . ((dynamicRegistration . t) 3669 (linkSupport . t))) 3670 (references . ((dynamicRegistration . t))) 3671 (implementation . ((dynamicRegistration . t) 3672 (linkSupport . t))) 3673 (typeDefinition . ((dynamicRegistration . t) 3674 (linkSupport . t))) 3675 (synchronization . ((willSave . t) (didSave . t) (willSaveWaitUntil . t))) 3676 (documentSymbol . ((symbolKind . ((valueSet . ,(apply 'vector (number-sequence 1 26))))) 3677 (hierarchicalDocumentSymbolSupport . t))) 3678 (formatting . ((dynamicRegistration . t))) 3679 (rangeFormatting . ((dynamicRegistration . t))) 3680 (onTypeFormatting . ((dynamicRegistration . t))) 3681 ,@(when (and lsp-semantic-tokens-enable 3682 (functionp 'lsp--semantic-tokens-capabilities)) 3683 (lsp--semantic-tokens-capabilities)) 3684 (rename . ((dynamicRegistration . t) (prepareSupport . t))) 3685 (codeAction . ((dynamicRegistration . t) 3686 (isPreferredSupport . t) 3687 (codeActionLiteralSupport . ((codeActionKind . ((valueSet . ["" 3688 "quickfix" 3689 "refactor" 3690 "refactor.extract" 3691 "refactor.inline" 3692 "refactor.rewrite" 3693 "source" 3694 "source.organizeImports"]))))) 3695 (resolveSupport . ((properties . ["edit" "command"]))) 3696 (dataSupport . t))) 3697 (completion . ((completionItem . ((snippetSupport . ,(cond 3698 ((and lsp-enable-snippet (not (fboundp 'yas-minor-mode))) 3699 (lsp--warn (concat 3700 "Yasnippet is not installed, but `lsp-enable-snippet' is set to `t'. " 3701 "You must either install yasnippet, or disable snippet support.")) 3702 :json-false) 3703 (lsp-enable-snippet t) 3704 (t :json-false))) 3705 (documentationFormat . ["markdown" "plaintext"]) 3706 ;; Remove this after jdtls support resolveSupport 3707 (resolveAdditionalTextEditsSupport . t) 3708 (insertReplaceSupport . t) 3709 (deprecatedSupport . t) 3710 (resolveSupport 3711 . ((properties . ["documentation" 3712 "detail" 3713 "additionalTextEdits" 3714 "command"]))) 3715 (insertTextModeSupport . ((valueSet . [1 2]))))) 3716 (contextSupport . t) 3717 (dynamicRegistration . t))) 3718 (signatureHelp . ((signatureInformation . ((parameterInformation . ((labelOffsetSupport . t))))) 3719 (dynamicRegistration . t))) 3720 (documentLink . ((dynamicRegistration . t) 3721 (tooltipSupport . t))) 3722 (hover . ((contentFormat . ["markdown" "plaintext"]) 3723 (dynamicRegistration . t))) 3724 ,@(when lsp-enable-folding 3725 `((foldingRange . ((dynamicRegistration . t) 3726 ,@(when lsp-folding-range-limit 3727 `((rangeLimit . ,lsp-folding-range-limit))) 3728 ,@(when lsp-folding-line-folding-only 3729 `((lineFoldingOnly . t))))))) 3730 (selectionRange . ((dynamicRegistration . t))) 3731 (callHierarchy . ((dynamicRegistration . :json-false))) 3732 (typeHierarchy . ((dynamicRegistration . t))) 3733 (publishDiagnostics . ((relatedInformation . t) 3734 (tagSupport . ((valueSet . [1 2]))) 3735 (versionSupport . t))) 3736 (linkedEditingRange . ((dynamicRegistration . t))))) 3737 (window . ((workDoneProgress . t) 3738 (showDocument . ((support . t)))))) 3739 custom-capabilities)) 3740 3741 (defun lsp-find-roots-for-workspace (workspace session) 3742 "Get all roots for the WORKSPACE." 3743 (-filter #'identity (ht-map (lambda (folder workspaces) 3744 (when (-contains? workspaces workspace) 3745 folder)) 3746 (lsp-session-folder->servers session)))) 3747 3748 (defun lsp-session-watches (&optional session) 3749 "Get watches created for SESSION." 3750 (or (gethash "__watches" (lsp-session-metadata (or session (lsp-session)))) 3751 (-let [res (make-hash-table :test 'equal)] 3752 (puthash "__watches" res (lsp-session-metadata (or session (lsp-session)))) 3753 res))) 3754 3755 (defun lsp--file-process-event (session root-folder event) 3756 "Process file event." 3757 (let* ((changed-file (cl-third event)) 3758 (rel-changed-file (f-relative changed-file root-folder)) 3759 (event-numeric-kind (alist-get (cl-second event) lsp--file-change-type)) 3760 (bit-position (1- event-numeric-kind)) 3761 (watch-bit (ash 1 bit-position))) 3762 (->> 3763 session 3764 lsp-session-folder->servers 3765 (gethash root-folder) 3766 (seq-do (lambda (workspace) 3767 (when (->> 3768 workspace 3769 lsp--workspace-registered-server-capabilities 3770 (-any? 3771 (lambda (capability) 3772 (and 3773 (equal (lsp--registered-capability-method capability) 3774 "workspace/didChangeWatchedFiles") 3775 (->> 3776 capability 3777 lsp--registered-capability-options 3778 (lsp:did-change-watched-files-registration-options-watchers) 3779 (seq-find 3780 (-lambda ((fs-watcher &as &FileSystemWatcher :glob-pattern :kind? :_cachedRegexp cached-regexp)) 3781 (when (or (null kind?) 3782 (> (logand kind? watch-bit) 0)) 3783 (-let [regexes (or cached-regexp 3784 (let ((regexp (lsp-glob-to-regexps glob-pattern))) 3785 (lsp-put fs-watcher :_cachedRegexp regexp) 3786 regexp))] 3787 (-any? (lambda (re) 3788 (or (string-match re changed-file) 3789 (string-match re rel-changed-file))) 3790 regexes)))))))))) 3791 (with-lsp-workspace workspace 3792 (lsp-notify 3793 "workspace/didChangeWatchedFiles" 3794 `((changes . [((type . ,event-numeric-kind) 3795 (uri . ,(lsp--path-to-uri changed-file)))])))))))))) 3796 3797 (lsp-defun lsp--server-register-capability ((&Registration :method :id :register-options?)) 3798 "Register capability REG." 3799 (when (and lsp-enable-file-watchers 3800 (equal method "workspace/didChangeWatchedFiles")) 3801 (-let* ((created-watches (lsp-session-watches (lsp-session))) 3802 (root-folders (cl-set-difference 3803 (lsp-find-roots-for-workspace lsp--cur-workspace (lsp-session)) 3804 (ht-keys created-watches)))) 3805 ;; create watch for each root folder without such 3806 (dolist (folder root-folders) 3807 (let* ((watch (make-lsp-watch :root-directory folder)) 3808 (ignored-things (lsp--get-ignored-regexes-for-workspace-root folder)) 3809 (ignored-files-regex-list (car ignored-things)) 3810 (ignored-directories-regex-list (cadr ignored-things))) 3811 (puthash folder watch created-watches) 3812 (lsp-watch-root-folder (file-truename folder) 3813 (-partial #'lsp--file-process-event (lsp-session) folder) 3814 ignored-files-regex-list 3815 ignored-directories-regex-list 3816 watch 3817 t))))) 3818 3819 (push 3820 (make-lsp--registered-capability :id id :method method :options register-options?) 3821 (lsp--workspace-registered-server-capabilities lsp--cur-workspace))) 3822 3823 (defmacro lsp--with-workspace-temp-buffer (workspace-root &rest body) 3824 "With a temp-buffer under `WORKSPACE-ROOT' and evaluate `BODY', useful to 3825 access dir-local variables." 3826 (declare (indent 1) (debug t)) 3827 `(with-temp-buffer 3828 ;; Set the buffer's name to something under the root so that we can hack the local variables 3829 ;; This file doesn't need to exist and will not be created due to this. 3830 (setq-local buffer-file-name (expand-file-name "lsp-mode-temp" (expand-file-name ,workspace-root))) 3831 (hack-local-variables) 3832 (prog1 ,@body 3833 (setq-local buffer-file-name nil)))) 3834 3835 (defun lsp--get-ignored-regexes-for-workspace-root (workspace-root) 3836 "Return a list of the form 3837 (lsp-file-watch-ignored-files lsp-file-watch-ignored-directories) for the given 3838 WORKSPACE-ROOT." 3839 ;; The intent of this function is to provide per-root workspace-level customization of the 3840 ;; lsp-file-watch-ignored-directories and lsp-file-watch-ignored-files variables. 3841 (lsp--with-workspace-temp-buffer workspace-root 3842 (list lsp-file-watch-ignored-files (lsp-file-watch-ignored-directories)))) 3843 3844 3845 (defun lsp--cleanup-hanging-watches () 3846 "Cleanup watches in case there are no more workspaces that are interested 3847 in that particular folder." 3848 (let* ((session (lsp-session)) 3849 (watches (lsp-session-watches session))) 3850 (dolist (watched-folder (ht-keys watches)) 3851 (when (-none? (lambda (workspace) 3852 (with-lsp-workspace workspace 3853 (lsp--registered-capability "workspace/didChangeWatchedFiles"))) 3854 (gethash watched-folder (lsp-session-folder->servers (lsp-session)))) 3855 (lsp-log "Cleaning up watches for folder %s. There is no workspace watching this folder..." watched-folder) 3856 (lsp-kill-watch (gethash watched-folder watches)) 3857 (remhash watched-folder watches))))) 3858 3859 (lsp-defun lsp--server-unregister-capability ((&Unregistration :id :method)) 3860 "Unregister capability UNREG." 3861 (setf (lsp--workspace-registered-server-capabilities lsp--cur-workspace) 3862 (seq-remove (lambda (e) (equal (lsp--registered-capability-id e) id)) 3863 (lsp--workspace-registered-server-capabilities lsp--cur-workspace))) 3864 (when (equal method "workspace/didChangeWatchedFiles") 3865 (lsp--cleanup-hanging-watches))) 3866 3867 (defun lsp--server-capabilities () 3868 "Return the capabilities of the language server associated with the buffer." 3869 (->> (lsp-workspaces) 3870 (-keep #'lsp--workspace-server-capabilities) 3871 (apply #'lsp-merge))) 3872 3873 (defun lsp--send-open-close-p () 3874 "Return whether open and close notifications should be sent to the server." 3875 (let ((sync (lsp:server-capabilities-text-document-sync? (lsp--server-capabilities)))) 3876 (or (memq sync '(1 2)) 3877 (lsp:text-document-sync-options-open-close? sync)))) 3878 3879 (defun lsp--send-will-save-p () 3880 "Return whether willSave notifications should be sent to the server." 3881 (-> (lsp--server-capabilities) 3882 (lsp:server-capabilities-text-document-sync?) 3883 (lsp:text-document-sync-options-will-save?))) 3884 3885 (defun lsp--send-will-save-wait-until-p () 3886 "Return whether willSaveWaitUntil notifications should be sent to the server." 3887 (-> (lsp--server-capabilities) 3888 (lsp:server-capabilities-text-document-sync?) 3889 (lsp:text-document-sync-options-will-save-wait-until?))) 3890 3891 (defun lsp--send-did-save-p () 3892 "Return whether didSave notifications should be sent to the server." 3893 (let ((sync (lsp:server-capabilities-text-document-sync? (lsp--server-capabilities)))) 3894 (or (memq sync '(1 2)) 3895 (lsp:text-document-sync-options-save? sync)))) 3896 3897 (defun lsp--save-include-text-p () 3898 "Return whether save notifications should include the text document's contents." 3899 (->> (lsp--server-capabilities) 3900 (lsp:server-capabilities-text-document-sync?) 3901 (lsp:text-document-sync-options-save?) 3902 (lsp:text-document-save-registration-options-include-text?))) 3903 3904 (defun lsp--send-will-rename-files-p (path) 3905 "Return whether willRenameFiles request should be sent to the server. 3906 If any filters, checks if it applies for PATH." 3907 (let* ((will-rename (-> (lsp--server-capabilities) 3908 (lsp:server-capabilities-workspace?) 3909 (lsp:workspace-server-capabilities-file-operations?) 3910 (lsp:workspace-file-operations-will-rename?))) 3911 (filters (seq-into (lsp:file-operation-registration-options-filters will-rename) 'list))) 3912 (and will-rename 3913 (or (seq-empty-p filters) 3914 (-any? (-lambda ((&FileOperationFilter :scheme? :pattern (&FileOperationPattern :glob))) 3915 (-let [regexes (lsp-glob-to-regexps glob)] 3916 (and (or (not scheme?) 3917 (string-prefix-p scheme? (lsp--path-to-uri path))) 3918 (-any? (lambda (re) 3919 (string-match re path)) 3920 regexes)))) 3921 filters))))) 3922 3923 (defun lsp--send-did-rename-files-p () 3924 "Return whether didRenameFiles notification should be sent to the server." 3925 (-> (lsp--server-capabilities) 3926 (lsp:server-capabilities-workspace?) 3927 (lsp:workspace-server-capabilities-file-operations?) 3928 (lsp:workspace-file-operations-did-rename?))) 3929 3930 (declare-function project-roots "ext:project" (project) t) 3931 (declare-function project-root "ext:project" (project) t) 3932 3933 (defun lsp--suggest-project-root () 3934 "Get project root." 3935 (or 3936 (when (featurep 'projectile) (condition-case nil 3937 (projectile-project-root) 3938 (error nil))) 3939 (when (featurep 'project) 3940 (when-let ((project (project-current))) 3941 (if (fboundp 'project-root) 3942 (project-root project) 3943 (car (with-no-warnings 3944 (project-roots project)))))) 3945 default-directory)) 3946 3947 (defun lsp--read-from-file (file) 3948 "Read FILE content." 3949 (when (file-exists-p file) 3950 (cl-first (read-from-string (f-read-text file 'utf-8))))) 3951 3952 (defun lsp--persist (file-name to-persist) 3953 "Persist TO-PERSIST in FILE-NAME. 3954 3955 This function creates the parent directories if they don't exist 3956 yet." 3957 (let ((print-length nil) 3958 (print-level nil)) 3959 ;; Create all parent directories: 3960 (make-directory (f-parent file-name) t) 3961 (f-write-text (prin1-to-string to-persist) 'utf-8 file-name))) 3962 3963 (defun lsp-workspace-folders-add (project-root) 3964 "Add PROJECT-ROOT to the list of workspace folders." 3965 (interactive 3966 (list (read-directory-name "Select folder to add: " 3967 (or (lsp--suggest-project-root) default-directory) nil t))) 3968 (cl-pushnew (lsp-f-canonical project-root) 3969 (lsp-session-folders (lsp-session)) :test 'equal) 3970 (lsp--persist-session (lsp-session)) 3971 3972 (run-hook-with-args 'lsp-workspace-folders-changed-functions (list project-root) nil)) 3973 3974 (defun lsp-workspace-folders-remove (project-root) 3975 "Remove PROJECT-ROOT from the list of workspace folders." 3976 (interactive (list (completing-read "Select folder to remove: " 3977 (lsp-session-folders (lsp-session)) 3978 nil t nil nil 3979 (lsp-find-session-folder (lsp-session) default-directory)))) 3980 3981 (setq project-root (lsp-f-canonical project-root)) 3982 3983 ;; send remove folder to each multiroot workspace associated with the folder 3984 (dolist (wks (->> (lsp-session) 3985 (lsp-session-folder->servers) 3986 (gethash project-root) 3987 (--filter (lsp--client-multi-root (lsp--workspace-client it))))) 3988 (with-lsp-workspace wks 3989 (lsp-notify "workspace/didChangeWorkspaceFolders" 3990 (lsp-make-did-change-workspace-folders-params 3991 :event (lsp-make-workspace-folders-change-event 3992 :removed (vector (lsp-make-workspace-folder 3993 :uri (lsp--path-to-uri project-root) 3994 :name (f-filename project-root))) 3995 :added []))))) 3996 3997 ;; turn off servers in the removed directory 3998 (let* ((session (lsp-session)) 3999 (folder->servers (lsp-session-folder->servers session)) 4000 (server-id->folders (lsp-session-server-id->folders session)) 4001 (workspaces (gethash project-root folder->servers))) 4002 4003 (remhash project-root folder->servers) 4004 4005 ;; turn off the servers without root folders 4006 (dolist (workspace workspaces) 4007 (when (--none? (-contains? it workspace) (ht-values folder->servers)) 4008 (lsp--info "Shutdown %s since folder %s is removed..." 4009 (lsp--workspace-print workspace) project-root) 4010 (with-lsp-workspace workspace (lsp--shutdown-workspace)))) 4011 4012 (setf (lsp-session-folders session) 4013 (-remove-item project-root (lsp-session-folders session))) 4014 4015 (ht-aeach (puthash key 4016 (-remove-item project-root value) 4017 server-id->folders) 4018 server-id->folders) 4019 (lsp--persist-session (lsp-session))) 4020 4021 (run-hook-with-args 'lsp-workspace-folders-changed-functions nil (list project-root))) 4022 4023 (defun lsp-workspace-blocklist-remove (project-root) 4024 "Remove PROJECT-ROOT from the workspace blocklist." 4025 (interactive (list (completing-read "Select folder to remove:" 4026 (lsp-session-folders-blocklist (lsp-session)) 4027 nil t))) 4028 (setf (lsp-session-folders-blocklist (lsp-session)) 4029 (delete project-root 4030 (lsp-session-folders-blocklist (lsp-session)))) 4031 (lsp--persist-session (lsp-session))) 4032 4033 (define-obsolete-function-alias 'lsp-workspace-folders-switch 4034 'lsp-workspace-folders-open "lsp-mode 6.1") 4035 4036 (defun lsp-workspace-folders-open (project-root) 4037 "Open the directory located at PROJECT-ROOT" 4038 (interactive (list (completing-read "Open folder: " 4039 (lsp-session-folders (lsp-session)) 4040 nil t))) 4041 (find-file project-root)) 4042 4043 (defun lsp--maybe-enable-signature-help (trigger-characters) 4044 (let ((ch last-command-event)) 4045 (when (cl-find ch trigger-characters :key #'string-to-char) 4046 (lsp-signature-activate)))) 4047 4048 (defun lsp--on-type-formatting-handler-create () 4049 (when-let ((provider (lsp--capability-for-method "textDocument/onTypeFormatting" ))) 4050 (-let [(&DocumentOnTypeFormattingOptions :more-trigger-character? 4051 :first-trigger-character) provider] 4052 (lambda () 4053 (lsp--on-type-formatting first-trigger-character 4054 more-trigger-character?))))) 4055 4056 (defun lsp--update-on-type-formatting-hook (&optional cleanup?) 4057 (let ((on-type-formatting-handler (lsp--on-type-formatting-handler-create))) 4058 (cond 4059 ((and lsp-enable-on-type-formatting on-type-formatting-handler (not cleanup?)) 4060 (add-hook 'post-self-insert-hook on-type-formatting-handler nil t)) 4061 ((or cleanup? 4062 (not lsp-enable-on-type-formatting)) 4063 (remove-hook 'post-self-insert-hook on-type-formatting-handler t))))) 4064 4065 (defun lsp--signature-help-handler-create () 4066 (-when-let ((&SignatureHelpOptions? :trigger-characters?) 4067 (lsp--capability-for-method "textDocument/signatureHelp")) 4068 (lambda () 4069 (lsp--maybe-enable-signature-help trigger-characters?)))) 4070 4071 (defun lsp--update-signature-help-hook (&optional cleanup?) 4072 (let ((signature-help-handler (lsp--signature-help-handler-create))) 4073 (cond 4074 ((and (or (equal lsp-signature-auto-activate t) 4075 (memq :on-trigger-char lsp-signature-auto-activate)) 4076 signature-help-handler) 4077 (add-hook 'post-self-insert-hook signature-help-handler nil t)) 4078 4079 ((or cleanup? 4080 (not (or (equal lsp-signature-auto-activate t) 4081 (memq :on-trigger-char lsp-signature-auto-activate)))) 4082 (remove-hook 'post-self-insert-hook signature-help-handler t))))) 4083 4084 (defun lsp--after-set-visited-file-name () 4085 (lsp-disconnect) 4086 (lsp)) 4087 4088 ;; TODO remove those eldoc workarounds when dropping support for Emacs 27 4089 ;; https://github.com/emacs-lsp/lsp-mode/issues/3295#issuecomment-1308994099 4090 (defvar eldoc-documentation-default) ; CI 4091 (when (< emacs-major-version 28) 4092 (unless (boundp 'eldoc-documentation-functions) 4093 (load "eldoc" nil 'nomessage)) 4094 (when (memq (default-value 'eldoc-documentation-function) '(nil ignore)) 4095 ;; actually `eldoc-documentation-strategy', but CI was failing 4096 (setq-default eldoc-documentation-function 'eldoc-documentation-default))) 4097 4098 (define-minor-mode lsp-managed-mode 4099 "Mode for source buffers managed by lsp-mode." 4100 :lighter nil 4101 (cond 4102 (lsp-managed-mode 4103 (when (lsp-feature? "textDocument/hover") 4104 (add-hook 'eldoc-documentation-functions #'lsp-eldoc-function nil t) 4105 (eldoc-mode 1)) 4106 4107 (add-hook 'after-change-functions #'lsp-on-change nil t) 4108 (add-hook 'after-revert-hook #'lsp-on-revert nil t) 4109 (add-hook 'after-save-hook #'lsp-on-save nil t) 4110 (add-hook 'auto-save-hook #'lsp--on-auto-save nil t) 4111 (add-hook 'before-change-functions #'lsp-before-change nil t) 4112 (add-hook 'before-save-hook #'lsp--before-save nil t) 4113 (add-hook 'kill-buffer-hook #'lsp--text-document-did-close nil t) 4114 (add-hook 'post-command-hook #'lsp--post-command nil t) 4115 4116 (lsp--update-on-type-formatting-hook) 4117 (lsp--update-signature-help-hook) 4118 4119 (when lsp-enable-xref 4120 (add-hook 'xref-backend-functions #'lsp--xref-backend nil t)) 4121 4122 (lsp-configure-buffer) 4123 4124 ;; make sure we turn off lsp-mode in case major mode changes, because major 4125 ;; mode change will wipe the buffer locals. 4126 (add-hook 'change-major-mode-hook #'lsp-disconnect nil t) 4127 (add-hook 'after-set-visited-file-name-hook #'lsp--after-set-visited-file-name nil t) 4128 4129 (let ((buffer (lsp-current-buffer))) 4130 (run-with-idle-timer 4131 0.0 nil 4132 (lambda () 4133 (when (lsp-buffer-live-p buffer) 4134 (lsp-with-current-buffer buffer 4135 (lsp--on-change-debounce buffer) 4136 (lsp--on-idle buffer))))))) 4137 (t 4138 (lsp-unconfig-buffer) 4139 4140 (remove-hook 'eldoc-documentation-functions #'lsp-eldoc-function t) 4141 (remove-hook 'post-command-hook #'lsp--post-command t) 4142 (remove-hook 'after-change-functions #'lsp-on-change t) 4143 (remove-hook 'after-revert-hook #'lsp-on-revert t) 4144 (remove-hook 'after-save-hook #'lsp-on-save t) 4145 (remove-hook 'auto-save-hook #'lsp--on-auto-save t) 4146 (remove-hook 'before-change-functions #'lsp-before-change t) 4147 (remove-hook 'before-save-hook #'lsp--before-save t) 4148 (remove-hook 'kill-buffer-hook #'lsp--text-document-did-close t) 4149 4150 (lsp--update-on-type-formatting-hook :cleanup) 4151 (lsp--update-signature-help-hook :cleanup) 4152 4153 (when lsp--on-idle-timer 4154 (cancel-timer lsp--on-idle-timer) 4155 (setq lsp--on-idle-timer nil)) 4156 4157 (remove-hook 'lsp-on-idle-hook #'lsp--document-links t) 4158 (remove-hook 'lsp-on-idle-hook #'lsp--document-highlight t) 4159 4160 (lsp--remove-overlays 'lsp-highlight) 4161 (lsp--remove-overlays 'lsp-links) 4162 4163 (remove-hook 'xref-backend-functions #'lsp--xref-backend t) 4164 (remove-hook 'change-major-mode-hook #'lsp-disconnect t) 4165 (remove-hook 'after-set-visited-file-name-hook #'lsp--after-set-visited-file-name t) 4166 (setq-local lsp-buffer-uri nil)))) 4167 4168 (defun lsp-configure-buffer () 4169 "Configure LSP features for current buffer." 4170 ;; make sure the core is running in the context of all available workspaces 4171 ;; to avoid misconfiguration in case we are running in `with-lsp-workspace' context 4172 (let ((lsp--buffer-workspaces (cond 4173 (lsp--buffer-workspaces) 4174 (lsp--cur-workspace (list lsp--cur-workspace)))) 4175 lsp--cur-workspace) 4176 (when lsp-auto-configure 4177 (lsp--auto-configure) 4178 4179 (when (and lsp-enable-text-document-color 4180 (lsp-feature? "textDocument/documentColor")) 4181 (add-hook 'lsp-on-change-hook #'lsp--document-color nil t)) 4182 4183 (when (and lsp-enable-imenu 4184 (lsp-feature? "textDocument/documentSymbol")) 4185 (lsp-enable-imenu)) 4186 4187 (when (and lsp-enable-indentation 4188 (lsp-feature? "textDocument/rangeFormatting")) 4189 (add-function :override (local 'indent-region-function) #'lsp-format-region)) 4190 4191 (when (and lsp-enable-symbol-highlighting 4192 (lsp-feature? "textDocument/documentHighlight")) 4193 (add-hook 'lsp-on-idle-hook #'lsp--document-highlight nil t)) 4194 4195 (when (and lsp-enable-links 4196 (lsp-feature? "textDocument/documentLink")) 4197 (add-hook 'lsp-on-idle-hook #'lsp--document-links nil t)) 4198 4199 (when (and lsp-inlay-hint-enable 4200 (lsp-feature? "textDocument/inlayHint")) 4201 (lsp-inlay-hints-mode)) 4202 4203 (when (and lsp-enable-dap-auto-configure 4204 (functionp 'dap-mode)) 4205 (dap-auto-configure-mode 1))) 4206 (run-hooks 'lsp-configure-hook))) 4207 4208 (defun lsp-unconfig-buffer () 4209 "Unconfigure LSP features for buffer." 4210 (lsp--remove-overlays 'lsp-color) 4211 4212 (when (advice-function-member-p 'lsp--imenu-create-index imenu-create-index-function) 4213 (remove-function (local 'imenu-create-index-function) #'lsp--imenu-create-index) 4214 (setq-local imenu-menubar-modified-tick 0) 4215 (setq-local imenu--index-alist nil) 4216 (imenu--cleanup)) 4217 4218 (remove-function (local 'indent-region-function) #'lsp-format-region) 4219 4220 (remove-hook 'lsp-on-change-hook #'lsp--document-color t) 4221 (remove-hook 'lsp-on-idle-hook #'lsp--document-highlight t) 4222 (remove-hook 'lsp-on-idle-hook #'lsp--document-links t) 4223 4224 (when (and lsp-enable-dap-auto-configure 4225 (functionp 'dap-mode)) 4226 (dap-auto-configure-mode -1)) 4227 4228 (run-hooks 'lsp-unconfigure-hook)) 4229 4230 (defun lsp--buffer-content () 4231 (lsp-save-restriction-and-excursion 4232 (or (lsp-virtual-buffer-call :buffer-string) 4233 (buffer-substring-no-properties (point-min) 4234 (point-max))))) 4235 4236 (defun lsp--text-document-did-open () 4237 "`document/didOpen' event." 4238 (run-hooks 'lsp-before-open-hook) 4239 (when (and lsp-auto-touch-files 4240 (not (f-exists? (lsp--uri-to-path (lsp--buffer-uri))))) 4241 (lsp--info "Saving file '%s' because it is not present on the disk." (lsp--buffer-uri)) 4242 (save-buffer)) 4243 4244 (setq lsp--cur-version (or lsp--cur-version 0)) 4245 (cl-pushnew (lsp-current-buffer) (lsp--workspace-buffers lsp--cur-workspace)) 4246 (lsp-notify 4247 "textDocument/didOpen" 4248 (list :textDocument 4249 (list :uri (lsp--buffer-uri) 4250 :languageId (lsp-buffer-language) 4251 :version lsp--cur-version 4252 :text (lsp--buffer-content)))) 4253 4254 (lsp-managed-mode 1) 4255 4256 (run-hooks 'lsp-after-open-hook) 4257 (when-let ((client (-some-> lsp--cur-workspace (lsp--workspace-client)))) 4258 (-some-> (lsp--client-after-open-fn client) 4259 (funcall)) 4260 (-some-> (format "lsp-%s-after-open-hook" (lsp--client-server-id client)) 4261 (intern-soft) 4262 (run-hooks)))) 4263 4264 (defun lsp--text-document-identifier () 4265 "Make TextDocumentIdentifier." 4266 (list :uri (lsp--buffer-uri))) 4267 4268 (defun lsp--versioned-text-document-identifier () 4269 "Make VersionedTextDocumentIdentifier." 4270 (plist-put (lsp--text-document-identifier) :version lsp--cur-version)) 4271 4272 (defun lsp--cur-line (&optional point) 4273 (1- (line-number-at-pos point))) 4274 4275 (defun lsp--cur-position () 4276 "Make a Position object for the current point." 4277 (or (lsp-virtual-buffer-call :cur-position) 4278 (lsp-save-restriction-and-excursion 4279 (list :line (lsp--cur-line) 4280 :character (- (point) (line-beginning-position)))))) 4281 4282 (defun lsp--point-to-position (point) 4283 "Convert POINT to Position." 4284 (lsp-save-restriction-and-excursion 4285 (goto-char point) 4286 (lsp--cur-position))) 4287 4288 (defun lsp--range (start end) 4289 "Make Range body from START and END." 4290 ;; make sure start and end are Position objects 4291 (list :start start :end end)) 4292 4293 (defun lsp--region-to-range (start end) 4294 "Make Range object for the current region." 4295 (lsp--range (lsp--point-to-position start) 4296 (lsp--point-to-position end))) 4297 4298 (defun lsp--region-or-line () 4299 "The active region or the current line." 4300 (if (use-region-p) 4301 (lsp--region-to-range (region-beginning) (region-end)) 4302 (lsp--region-to-range (line-beginning-position) (line-end-position)))) 4303 4304 (defun lsp--check-document-changes-version (document-changes) 4305 "Verify that DOCUMENT-CHANGES have the proper version." 4306 (unless (seq-every-p 4307 (-lambda ((&TextDocumentEdit :text-document)) 4308 (or 4309 (not text-document) 4310 (let* ((filename (-> text-document 4311 lsp:versioned-text-document-identifier-uri 4312 lsp--uri-to-path)) 4313 (version (lsp:versioned-text-document-identifier-version? text-document))) 4314 (with-current-buffer (find-file-noselect filename) 4315 (or (null version) (zerop version) (= -1 version) 4316 (equal version lsp--cur-version)))))) 4317 document-changes) 4318 (error "Document changes cannot be applied due to different document version"))) 4319 4320 (defun lsp--apply-workspace-edit (workspace-edit &optional operation) 4321 "Apply the WorkspaceEdit object WORKSPACE-EDIT. 4322 OPERATION is symbol representing the source of this text edit." 4323 (-let (((&WorkspaceEdit :document-changes? :changes?) workspace-edit)) 4324 (if-let ((document-changes (seq-reverse document-changes?))) 4325 (progn 4326 (lsp--check-document-changes-version document-changes) 4327 (->> document-changes 4328 (seq-filter (-lambda ((&CreateFile :kind)) (equal kind "create"))) 4329 (seq-do (lambda (change) (lsp--apply-text-document-edit change operation)))) 4330 (->> document-changes 4331 (seq-filter (-lambda ((&CreateFile :kind)) 4332 (and (or (not kind) (equal kind "edit")) 4333 (not (equal kind "create"))))) 4334 (seq-do (lambda (change) (lsp--apply-text-document-edit change operation)))) 4335 (->> document-changes 4336 (seq-filter (-lambda ((&CreateFile :kind)) 4337 (and (not (or (not kind) (equal kind "edit"))) 4338 (not (equal kind "create"))))) 4339 (seq-do (lambda (change) (lsp--apply-text-document-edit change operation))))) 4340 (lsp-map 4341 (lambda (uri text-edits) 4342 (with-current-buffer (-> uri lsp--uri-to-path find-file-noselect) 4343 (lsp--apply-text-edits text-edits operation))) 4344 changes?)))) 4345 4346 (defmacro lsp-with-filename (file &rest body) 4347 "Execute BODY with FILE as a context. 4348 Need to handle the case when FILE indicates virtual buffer." 4349 (declare (indent 1) (debug t)) 4350 `(if-let ((lsp--virtual-buffer (get-text-property 0 'lsp-virtual-buffer ,file))) 4351 (lsp-with-current-buffer lsp--virtual-buffer 4352 ,@body) 4353 ,@body)) 4354 4355 (defun lsp--apply-text-document-edit (edit &optional operation) 4356 "Apply the TextDocumentEdit object EDIT. 4357 OPERATION is symbol representing the source of this text edit. 4358 If the file is not being visited by any buffer, it is opened with 4359 `find-file-noselect'. 4360 Because lsp-mode does not store previous document versions, the edit is only 4361 applied if the version of the textDocument matches the version of the 4362 corresponding file. 4363 4364 interface TextDocumentEdit { 4365 textDocument: VersionedTextDocumentIdentifier; 4366 edits: TextEdit[]; 4367 }" 4368 (pcase (lsp:edit-kind edit) 4369 ("create" (-let* (((&CreateFile :uri :options?) edit) 4370 (file-name (lsp--uri-to-path uri))) 4371 (mkdir (f-dirname file-name) t) 4372 (f-touch file-name) 4373 (when (lsp:create-file-options-overwrite? options?) 4374 (f-write-text "" nil file-name)) 4375 (find-file-noselect file-name))) 4376 ("delete" (-let (((&DeleteFile :uri :options? (&DeleteFileOptions? :recursive?)) edit)) 4377 (f-delete (lsp--uri-to-path uri) recursive?))) 4378 ("rename" (-let* (((&RenameFile :old-uri :new-uri :options? (&RenameFileOptions? :overwrite?)) edit) 4379 (old-file-name (lsp--uri-to-path old-uri)) 4380 (new-file-name (lsp--uri-to-path new-uri)) 4381 (buf (find-buffer-visiting old-file-name))) 4382 (when buf 4383 (lsp-with-current-buffer buf 4384 (save-buffer) 4385 (lsp--text-document-did-close))) 4386 (mkdir (f-dirname new-file-name) t) 4387 (rename-file old-file-name new-file-name overwrite?) 4388 (when buf 4389 (lsp-with-current-buffer buf 4390 (set-buffer-modified-p nil) 4391 (setq lsp-buffer-uri nil) 4392 (set-visited-file-name new-file-name) 4393 (lsp))))) 4394 (_ (let ((file-name (->> edit 4395 (lsp:text-document-edit-text-document) 4396 (lsp:versioned-text-document-identifier-uri) 4397 (lsp--uri-to-path)))) 4398 (lsp-with-current-buffer (find-buffer-visiting file-name) 4399 (lsp-with-filename file-name 4400 (lsp--apply-text-edits (lsp:text-document-edit-edits edit) operation))))))) 4401 4402 (lsp-defun lsp--position-compare ((&Position :line left-line 4403 :character left-character) 4404 (&Position :line right-line 4405 :character right-character)) 4406 "Return t if position LEFT is greater than RIGHT." 4407 (if (= left-line right-line) 4408 (> left-character right-character) 4409 (> left-line right-line))) 4410 4411 (lsp-defun lsp-point-in-range? (position (&Range :start :end)) 4412 "Returns if POINT is in RANGE." 4413 (not (or (lsp--position-compare start position) 4414 (lsp--position-compare position end)))) 4415 4416 (lsp-defun lsp--position-equal ((&Position :line left-line 4417 :character left-character) 4418 (&Position :line right-line 4419 :character right-character)) 4420 "Return whether LEFT and RIGHT positions are equal." 4421 (and (= left-line right-line) 4422 (= left-character right-character))) 4423 4424 (lsp-defun lsp--text-edit-sort-predicate ((&TextEdit :range (&Range :start left-start :end left-end)) 4425 (&TextEdit :range (&Range :start right-start :end right-end))) 4426 (if (lsp--position-equal left-start right-start) 4427 (lsp--position-compare left-end right-end) 4428 (lsp--position-compare left-start right-start))) 4429 4430 (lsp-defun lsp--apply-text-edit ((edit &as &TextEdit :range (&RangeToPoint :start :end) :new-text)) 4431 "Apply the edits described in the TextEdit object in TEXT-EDIT." 4432 (setq new-text (s-replace "\r" "" (or new-text ""))) 4433 (lsp:set-text-edit-new-text edit new-text) 4434 (goto-char start) 4435 (delete-region start end) 4436 (insert new-text)) 4437 4438 ;; WORKAROUND: typescript-language might send -1 when applying code actions. 4439 ;; see https://github.com/emacs-lsp/lsp-mode/issues/1582 4440 (lsp-defun lsp--fix-point ((point &as &Position :character :line)) 4441 (-doto point 4442 (lsp:set-position-line (max 0 line)) 4443 (lsp:set-position-character (max 0 character)))) 4444 4445 (lsp-defun lsp--apply-text-edit-replace-buffer-contents ((edit &as 4446 &TextEdit 4447 :range (&Range :start :end) 4448 :new-text)) 4449 "Apply the edits described in the TextEdit object in TEXT-EDIT. 4450 The method uses `replace-buffer-contents'." 4451 (setq new-text (s-replace "\r" "" (or new-text ""))) 4452 (lsp:set-text-edit-new-text edit new-text) 4453 (-let* ((source (current-buffer)) 4454 ((beg . end) (lsp--range-to-region (lsp-make-range :start (lsp--fix-point start) 4455 :end (lsp--fix-point end))))) 4456 (with-temp-buffer 4457 (insert new-text) 4458 (let ((temp (current-buffer))) 4459 (with-current-buffer source 4460 (save-excursion 4461 (save-restriction 4462 (narrow-to-region beg end) 4463 4464 ;; On emacs versions < 26.2, 4465 ;; `replace-buffer-contents' is buggy - it calls 4466 ;; change functions with invalid arguments - so we 4467 ;; manually call the change functions here. 4468 ;; 4469 ;; See emacs bugs #32237, #32278: 4470 ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32237 4471 ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32278 4472 (let ((inhibit-modification-hooks t) 4473 (length (- end beg))) 4474 (run-hook-with-args 'before-change-functions 4475 beg end) 4476 (replace-buffer-contents temp) 4477 (run-hook-with-args 'after-change-functions 4478 beg (+ beg (length new-text)) 4479 length))))))))) 4480 4481 (defun lsp--to-yasnippet-snippet (snippet) 4482 "Convert LSP SNIPPET to yasnippet snippet." 4483 ;; LSP snippet doesn't escape "{" and "`", but yasnippet requires escaping it. 4484 (replace-regexp-in-string (rx (or bos (not (any "$" "\\"))) (group (or "{" "`"))) 4485 (rx "\\" (backref 1)) 4486 snippet 4487 nil nil 1)) 4488 4489 (defvar-local lsp-enable-relative-indentation nil 4490 "Enable relative indentation when insert texts, snippets ... 4491 from language server.") 4492 4493 (defun lsp--expand-snippet (snippet &optional start end expand-env) 4494 "Wrapper of `yas-expand-snippet' with all of it arguments. 4495 The snippet will be convert to LSP style and indent according to 4496 LSP server result." 4497 (require 'yasnippet nil t) 4498 (let* ((inhibit-field-text-motion t) 4499 (yas-wrap-around-region nil) 4500 (yas-indent-line 'none) 4501 (yas-also-auto-indent-first-line nil)) 4502 (yas-expand-snippet 4503 (lsp--to-yasnippet-snippet snippet) 4504 start end expand-env))) 4505 4506 (defun lsp--indent-lines (start end &optional insert-text-mode?) 4507 "Indent from START to END based on INSERT-TEXT-MODE? value. 4508 - When INSERT-TEXT-MODE? is provided 4509 - if it's `lsp/insert-text-mode-as-it', do no editor indentation. 4510 - if it's `lsp/insert-text-mode-adjust-indentation', adjust leading 4511 whitespaces to match the line where text is inserted. 4512 - When it's not provided, using `indent-line-function' for each line." 4513 (save-excursion 4514 (goto-char end) 4515 (let* ((end-line (line-number-at-pos)) 4516 (offset (save-excursion 4517 (goto-char start) 4518 (current-indentation))) 4519 (indent-line-function 4520 (cond ((equal insert-text-mode? lsp/insert-text-mode-as-it) 4521 #'ignore) 4522 ((or (equal insert-text-mode? lsp/insert-text-mode-adjust-indentation) 4523 lsp-enable-relative-indentation 4524 ;; Indenting snippets is extremely slow in `org-mode' buffers 4525 ;; since it has to calculate indentation based on SRC block 4526 ;; position. Thus we use relative indentation as default. 4527 (derived-mode-p 'org-mode)) 4528 (lambda () (save-excursion 4529 (beginning-of-line) 4530 (indent-to-column offset)))) 4531 (t indent-line-function)))) 4532 (goto-char start) 4533 (forward-line) 4534 (while (and (not (eobp)) 4535 (<= (line-number-at-pos) end-line)) 4536 (funcall indent-line-function) 4537 (forward-line))))) 4538 4539 (defun lsp--apply-text-edits (edits &optional operation) 4540 "Apply the EDITS described in the TextEdit[] object. 4541 OPERATION is symbol representing the source of this text edit." 4542 (unless (seq-empty-p edits) 4543 (atomic-change-group 4544 (run-hooks 'lsp-before-apply-edits-hook) 4545 (let* ((change-group (prepare-change-group)) 4546 (howmany (length edits)) 4547 (message (format "Applying %s edits to `%s' ..." howmany (current-buffer))) 4548 (_ (lsp--info message)) 4549 (reporter (make-progress-reporter message 0 howmany)) 4550 (done 0) 4551 (apply-edit (if (not lsp--virtual-buffer) 4552 #'lsp--apply-text-edit-replace-buffer-contents 4553 #'lsp--apply-text-edit))) 4554 (unwind-protect 4555 (->> edits 4556 ;; We sort text edits so as to apply edits that modify latter 4557 ;; parts of the document first. Furthermore, because the LSP 4558 ;; spec dictates that: "If multiple inserts have the same 4559 ;; position, the order in the array defines which edit to 4560 ;; apply first." We reverse the initial list and sort stably 4561 ;; to make sure the order among edits with the same position 4562 ;; is preserved. 4563 (nreverse) 4564 (seq-sort #'lsp--text-edit-sort-predicate) 4565 (mapc (lambda (edit) 4566 (progress-reporter-update reporter (cl-incf done)) 4567 (funcall apply-edit edit) 4568 (when (lsp:snippet-text-edit-insert-text-format? edit) 4569 (-when-let ((&SnippetTextEdit :range (&RangeToPoint :start) 4570 :insert-text-format? :new-text) edit) 4571 (when (eq insert-text-format? lsp/insert-text-format-snippet) 4572 ;; No `save-excursion' needed since expand snippet will change point anyway 4573 (goto-char (+ start (length new-text))) 4574 (lsp--indent-lines start (point)) 4575 (lsp--expand-snippet new-text start (point))))) 4576 (run-hook-with-args 'lsp-after-apply-edits-hook operation)))) 4577 (undo-amalgamate-change-group change-group) 4578 (progress-reporter-done reporter)))))) 4579 4580 (defun lsp--create-apply-text-edits-handlers () 4581 "Create (handler cleanup-fn) for applying text edits in async request. 4582 Only works when mode is `tick or `alive." 4583 (let* (first-edited 4584 (func (lambda (start &rest _) 4585 (setq first-edited (if first-edited 4586 (min start first-edited) 4587 start))))) 4588 (add-hook 'before-change-functions func nil t) 4589 (list 4590 (lambda (edits) 4591 (if (and first-edited 4592 (seq-find (-lambda ((&TextEdit :range (&RangeToPoint :end))) 4593 ;; Text edit region is overlapped 4594 (> end first-edited)) 4595 edits)) 4596 (lsp--warn "TextEdits will not be applied since document has been modified before of them.") 4597 (lsp--apply-text-edits edits 'completion-cleanup))) 4598 (lambda () 4599 (remove-hook 'before-change-functions func t))))) 4600 4601 (defun lsp--capability (cap &optional capabilities) 4602 "Get the value of capability CAP. If CAPABILITIES is non-nil, use them instead." 4603 (when (stringp cap) 4604 (setq cap (intern (concat ":" cap)))) 4605 4606 (lsp-get (or capabilities 4607 (lsp--server-capabilities)) 4608 cap)) 4609 4610 (defun lsp--registered-capability (method) 4611 "Check whether there is workspace providing METHOD." 4612 (->> (lsp-workspaces) 4613 (--keep (seq-find (lambda (reg) 4614 (equal (lsp--registered-capability-method reg) method)) 4615 (lsp--workspace-registered-server-capabilities it))) 4616 cl-first)) 4617 4618 (defun lsp--capability-for-method (method) 4619 "Get the value of capability for METHOD." 4620 (-let* ((reqs (cdr (assoc method lsp-method-requirements))) 4621 ((&plist :capability) reqs)) 4622 (or (and capability (lsp--capability capability)) 4623 (-some-> (lsp--registered-capability method) 4624 (lsp--registered-capability-options))))) 4625 4626 (defvar-local lsp--before-change-vals nil 4627 "Store the positions from the `lsp-before-change' function call, for 4628 validation and use in the `lsp-on-change' function.") 4629 4630 (defun lsp--text-document-content-change-event (start end length) 4631 "Make a TextDocumentContentChangeEvent body for START to END, of length LENGTH." 4632 ;; So (47 54 0) means add 7 chars starting at pos 47 4633 ;; must become 4634 ;; {"range":{"start":{"line":5,"character":6} 4635 ;; ,"end" :{"line":5,"character":6}} 4636 ;; ,"rangeLength":0 4637 ;; ,"text":"\nbb = 5"} 4638 ;; 4639 ;; And (47 47 7) means delete 7 chars starting at pos 47 4640 ;; must become 4641 ;; {"range":{"start":{"line":6,"character":0} 4642 ;; ,"end" :{"line":7,"character":0}} 4643 ;; ,"rangeLength":7 4644 ;; ,"text":""} 4645 ;; 4646 ;; (208 221 3) means delete 3 chars starting at pos 208, and replace them with 4647 ;; 13 chars. So it must become 4648 ;; {"range":{"start":{"line":5,"character":8} 4649 ;; ,"end" :{"line":5,"character":11}} 4650 ;; ,"rangeLength":3 4651 ;; ,"text":"new-chars-xxx"} 4652 ;; 4653 4654 ;; Adding text: 4655 ;; lsp-before-change:(start,end)=(33,33) 4656 ;; lsp-on-change:(start,end,length)=(33,34,0) 4657 ;; 4658 ;; Changing text: 4659 ;; lsp-before-change:(start,end)=(208,211) 4660 ;; lsp-on-change:(start,end,length)=(208,221,3) 4661 ;; 4662 ;; Deleting text: 4663 ;; lsp-before-change:(start,end)=(19,27) 4664 ;; lsp-on-change:(start,end,length)=(19,19,8) 4665 (if (zerop length) 4666 ;; Adding something only, work from start only 4667 `( :range ,(lsp--range 4668 (lsp--point-to-position start) 4669 (lsp--point-to-position start)) 4670 :rangeLength 0 4671 :text ,(buffer-substring-no-properties start end)) 4672 4673 (if (eq start end) 4674 ;; Deleting something only 4675 (if (lsp--bracketed-change-p start length) 4676 ;; The before-change value is bracketed, use it 4677 `( :range ,(lsp--range 4678 (lsp--point-to-position start) 4679 (plist-get lsp--before-change-vals :end-pos)) 4680 :rangeLength ,length 4681 :text "") 4682 ;; If the change is not bracketed, send a full change event instead. 4683 (lsp--full-change-event)) 4684 4685 ;; Deleting some things, adding others 4686 (if (lsp--bracketed-change-p start length) 4687 ;; The before-change value is valid, use it 4688 `( :range ,(lsp--range 4689 (lsp--point-to-position start) 4690 (plist-get lsp--before-change-vals :end-pos)) 4691 :rangeLength ,length 4692 :text ,(buffer-substring-no-properties start end)) 4693 (lsp--full-change-event))))) 4694 4695 (defun lsp--bracketed-change-p (start length) 4696 "If the before and after positions are the same, and the length 4697 is the size of the start range, we are probably good." 4698 (-let [(&plist :end before-end :start before-start) lsp--before-change-vals] 4699 (and (eq start before-start) 4700 (eq length (- before-end before-start))))) 4701 4702 (defun lsp--full-change-event () 4703 `(:text ,(lsp--buffer-content))) 4704 4705 (defun lsp-before-change (start end) 4706 "Executed before a file is changed. 4707 Added to `before-change-functions'." 4708 ;; Note: 4709 ;; 4710 ;; This variable holds a list of functions to call when Emacs is about to 4711 ;; modify a buffer. Each function gets two arguments, the beginning and end of 4712 ;; the region that is about to change, represented as integers. The buffer 4713 ;; that is about to change is always the current buffer when the function is 4714 ;; called. 4715 ;; 4716 ;; WARNING: 4717 ;; 4718 ;; Do not expect the before-change hooks and the after-change hooks be called 4719 ;; in balanced pairs around each buffer change. Also don't expect the 4720 ;; before-change hooks to be called for every chunk of text Emacs is about to 4721 ;; delete. These hooks are provided on the assumption that Lisp programs will 4722 ;; use either before- or the after-change hooks, but not both, and the 4723 ;; boundaries of the region where the changes happen might include more than 4724 ;; just the actual changed text, or even lump together several changes done 4725 ;; piecemeal. 4726 (save-match-data 4727 (lsp-save-restriction-and-excursion 4728 (setq lsp--before-change-vals 4729 (list :start start 4730 :end end 4731 :end-pos (lsp--point-to-position end)))))) 4732 4733 (defun lsp--flush-delayed-changes () 4734 (let ((inhibit-quit t)) 4735 (when lsp--delay-timer 4736 (cancel-timer lsp--delay-timer)) 4737 (mapc (-lambda ((workspace buffer document change)) 4738 (with-current-buffer buffer 4739 (with-lsp-workspace workspace 4740 (lsp-notify "textDocument/didChange" 4741 (list :textDocument document 4742 :contentChanges (vector change)))))) 4743 (prog1 (nreverse lsp--delayed-requests) 4744 (setq lsp--delayed-requests nil))))) 4745 4746 (defun lsp--workspace-sync-method (workspace) 4747 (let ((sync (-> workspace 4748 (lsp--workspace-server-capabilities) 4749 (lsp:server-capabilities-text-document-sync?)))) 4750 (if (lsp-text-document-sync-options? sync) 4751 (lsp:text-document-sync-options-change? sync) 4752 sync))) 4753 4754 (defun lsp-on-change (start end length &optional content-change-event-fn) 4755 "Executed when a file is changed. 4756 Added to `after-change-functions'." 4757 ;; Note: 4758 ;; 4759 ;; Each function receives three arguments: the beginning and end of the region 4760 ;; just changed, and the length of the text that existed before the change. 4761 ;; All three arguments are integers. The buffer that has been changed is 4762 ;; always the current buffer when the function is called. 4763 ;; 4764 ;; The length of the old text is the difference between the buffer positions 4765 ;; before and after that text as it was before the change. As for the 4766 ;; changed text, its length is simply the difference between the first two 4767 ;; arguments. 4768 ;; 4769 ;; So (47 54 0) means add 7 chars starting at pos 47 4770 ;; So (47 47 7) means delete 7 chars starting at pos 47 4771 (save-match-data 4772 (let ((inhibit-quit t) 4773 ;; make sure that `lsp-on-change' is called in multi-workspace context 4774 ;; see #2901 4775 lsp--cur-workspace) 4776 ;; A (revert-buffer) call with the 'preserve-modes parameter (eg, as done 4777 ;; by auto-revert-mode) will cause this handler to get called with a nil 4778 ;; buffer-file-name. We need the buffer-file-name to send notifications; 4779 ;; so we skip handling revert-buffer-caused changes and instead handle 4780 ;; reverts separately in lsp-on-revert 4781 (when (not revert-buffer-in-progress-p) 4782 (cl-incf lsp--cur-version) 4783 (mapc 4784 (lambda (workspace) 4785 (pcase (or lsp-document-sync-method 4786 (lsp--workspace-sync-method workspace)) 4787 (1 4788 (if lsp-debounce-full-sync-notifications 4789 (setq lsp--delayed-requests 4790 (->> lsp--delayed-requests 4791 (-remove (-lambda ((_ buffer)) 4792 (equal (current-buffer) buffer))) 4793 (cons (list workspace 4794 (current-buffer) 4795 (lsp--versioned-text-document-identifier) 4796 (lsp--full-change-event))))) 4797 (with-lsp-workspace workspace 4798 (lsp-notify "textDocument/didChange" 4799 (list :contentChanges (vector (lsp--full-change-event)) 4800 :textDocument (lsp--versioned-text-document-identifier)))))) 4801 (2 4802 (with-lsp-workspace workspace 4803 (lsp-notify 4804 "textDocument/didChange" 4805 (list :textDocument (lsp--versioned-text-document-identifier) 4806 :contentChanges (vector 4807 (if content-change-event-fn 4808 (funcall content-change-event-fn start end length) 4809 (lsp--text-document-content-change-event 4810 start end length))))))))) 4811 (lsp-workspaces)) 4812 (when lsp--delay-timer (cancel-timer lsp--delay-timer)) 4813 (setq lsp--delay-timer (run-with-idle-timer 4814 lsp-debounce-full-sync-notifications-interval 4815 nil 4816 #'lsp--flush-delayed-changes)) 4817 ;; force cleanup overlays after each change 4818 (lsp--remove-overlays 'lsp-highlight) 4819 (lsp--after-change (current-buffer)) 4820 (setq lsp--signature-last-index nil 4821 lsp--signature-last nil) 4822 ;; cleanup diagnostics 4823 (when lsp-diagnostic-clean-after-change 4824 (lsp-foreach-workspace 4825 (-let [diagnostics (lsp--workspace-diagnostics lsp--cur-workspace)] 4826 (remhash (lsp--fix-path-casing (buffer-file-name)) diagnostics)))))))) 4827 4828 4829 4830 ;; facilities for on change hooks. We do not want to make lsp calls on each 4831 ;; change event so we add debounce to avoid flooding the server with events. 4832 ;; Additionally, we want to have a mechanism for stopping the server calls in 4833 ;; particular cases like, e. g. when performing completion. 4834 4835 (defvar lsp-inhibit-lsp-hooks nil 4836 "Flag to control.") 4837 4838 (defcustom lsp-on-change-hook nil 4839 "Hooks to run when buffer has changed." 4840 :type 'hook 4841 :group 'lsp-mode) 4842 4843 (defcustom lsp-idle-delay 0.500 4844 "Debounce interval for `after-change-functions'." 4845 :type 'number 4846 :group 'lsp-mode) 4847 4848 (defcustom lsp-on-idle-hook nil 4849 "Hooks to run after `lsp-idle-delay'." 4850 :type 'hook 4851 :group 'lsp-mode) 4852 4853 (defun lsp--idle-reschedule (buffer) 4854 (when lsp--on-idle-timer 4855 (cancel-timer lsp--on-idle-timer)) 4856 4857 (setq lsp--on-idle-timer (run-with-idle-timer 4858 lsp-idle-delay 4859 nil 4860 #'lsp--on-idle 4861 buffer))) 4862 4863 (defun lsp--post-command () 4864 (lsp--cleanup-highlights-if-needed) 4865 (lsp--idle-reschedule (current-buffer))) 4866 4867 (defun lsp--on-idle (buffer) 4868 "Start post command loop." 4869 (when (and (buffer-live-p buffer) 4870 (equal buffer (current-buffer)) 4871 (not lsp-inhibit-lsp-hooks) 4872 lsp-managed-mode) 4873 (run-hooks 'lsp-on-idle-hook))) 4874 4875 (defun lsp--on-change-debounce (buffer) 4876 (when (and (buffer-live-p buffer) 4877 (equal buffer (current-buffer)) 4878 (not lsp-inhibit-lsp-hooks) 4879 lsp-managed-mode) 4880 (run-hooks 'lsp-on-change-hook))) 4881 4882 (defun lsp--after-change (buffer) 4883 (when (fboundp 'lsp--semantic-tokens-refresh-if-enabled) 4884 (lsp--semantic-tokens-refresh-if-enabled buffer)) 4885 (when lsp--on-change-timer 4886 (cancel-timer lsp--on-change-timer)) 4887 (setq lsp--on-change-timer (run-with-idle-timer 4888 lsp-idle-delay 4889 nil 4890 #'lsp--on-change-debounce 4891 buffer)) 4892 (lsp--idle-reschedule buffer)) 4893 4894 4895 (defcustom lsp-trim-trailing-whitespace t 4896 "Trim trailing whitespace on a line." 4897 :group 'lsp-mode 4898 :type 'boolean) 4899 4900 (defcustom lsp-insert-final-newline t 4901 "Insert a newline character at the end of the file if one does not exist." 4902 :group 'lsp-mode 4903 :type 'boolean) 4904 4905 (defcustom lsp-trim-final-newlines t 4906 "Trim all newlines after the final newline at the end of the file." 4907 :group 'lsp-mode 4908 :type 'boolean) 4909 4910 4911 (defun lsp--on-type-formatting (first-trigger-characters more-trigger-characters) 4912 "Self insert handling. 4913 Applies on type formatting." 4914 (let ((ch last-command-event)) 4915 (when (or (eq (string-to-char first-trigger-characters) ch) 4916 (cl-find ch more-trigger-characters :key #'string-to-char)) 4917 (lsp-request-async "textDocument/onTypeFormatting" 4918 (lsp-make-document-on-type-formatting-params 4919 :text-document (lsp--text-document-identifier) 4920 :options (lsp-make-formatting-options 4921 :tab-size (symbol-value (lsp--get-indent-width major-mode)) 4922 :insert-spaces (lsp-json-bool (not indent-tabs-mode)) 4923 :trim-trailing-whitespace? (lsp-json-bool lsp-trim-trailing-whitespace) 4924 :insert-final-newline? (lsp-json-bool lsp-insert-final-newline) 4925 :trim-final-newlines? (lsp-json-bool lsp-trim-final-newlines)) 4926 :ch (char-to-string ch) 4927 :position (lsp--cur-position)) 4928 (lambda (data) (lsp--apply-text-edits data 'format)) 4929 :mode 'tick)))) 4930 4931 4932 ;; links 4933 (defun lsp--document-links () 4934 (when (lsp-feature? "textDocument/documentLink") 4935 (lsp-request-async 4936 "textDocument/documentLink" 4937 `(:textDocument ,(lsp--text-document-identifier)) 4938 (lambda (links) 4939 (lsp--remove-overlays 'lsp-link) 4940 (seq-do 4941 (-lambda ((link &as &DocumentLink :range (&Range :start :end))) 4942 (-doto (make-button (lsp--position-to-point start) 4943 (lsp--position-to-point end) 4944 'action (lsp--document-link-keymap link) 4945 'keymap (let ((map (make-sparse-keymap))) 4946 (define-key map [M-return] 'push-button) 4947 (define-key map [mouse-2] 'push-button) 4948 map) 4949 'help-echo "mouse-2, M-RET: Visit this link") 4950 (overlay-put 'lsp-link t))) 4951 links)) 4952 :mode 'unchanged))) 4953 4954 (defun lsp--document-link-handle-target (url) 4955 (let* ((parsed-url (url-generic-parse-url (url-unhex-string url))) 4956 (type (url-type parsed-url))) 4957 (pcase type 4958 ("file" 4959 (xref-push-marker-stack) 4960 (find-file (lsp--uri-to-path url)) 4961 (-when-let ((_ line column) (s-match (rx "#" (group (1+ num)) (or "," "#") (group (1+ num))) url)) 4962 (goto-char (lsp--position-to-point 4963 (lsp-make-position :character (1- (string-to-number column)) 4964 :line (1- (string-to-number line))))))) 4965 ((or "http" "https") (browse-url url)) 4966 (type (if-let ((handler (lsp--get-uri-handler type))) 4967 (funcall handler url) 4968 (signal 'lsp-file-scheme-not-supported (list url))))))) 4969 4970 (lsp-defun lsp--document-link-keymap ((link &as &DocumentLink :target?)) 4971 (if target? 4972 (lambda (_) 4973 (interactive) 4974 (lsp--document-link-handle-target target?)) 4975 (lambda (_) 4976 (interactive) 4977 (when (lsp:document-link-registration-options-resolve-provider? 4978 (lsp--capability-for-method "textDocument/documentLink")) 4979 (lsp-request-async 4980 "documentLink/resolve" 4981 link 4982 (-lambda ((&DocumentLink :target?)) 4983 (lsp--document-link-handle-target target?))))))) 4984 4985 4986 4987 (defcustom lsp-warn-no-matched-clients t 4988 "Whether to show messages when there are no supported clients." 4989 :group 'lsp-mode 4990 :type 'boolean) 4991 4992 (defun lsp-buffer-language--configured-id () 4993 "Return nil when not registered." 4994 (->> lsp-language-id-configuration 4995 (-first 4996 (-lambda ((mode-or-pattern . language)) 4997 (cond 4998 ((and (stringp mode-or-pattern) 4999 (s-matches? mode-or-pattern (buffer-file-name))) 5000 language) 5001 ((eq mode-or-pattern major-mode) language)))) 5002 cl-rest)) 5003 5004 (defvar-local lsp--buffer-language nil 5005 "Locally cached returned value of `lsp-buffer-language'.") 5006 5007 (defun lsp-buffer-language () 5008 "Get language corresponding current buffer." 5009 (or lsp--buffer-language 5010 (let* ((configured-language (lsp-buffer-language--configured-id))) 5011 (setq lsp--buffer-language 5012 (or configured-language 5013 ;; ensure non-nil 5014 (string-remove-suffix "-mode" (symbol-name major-mode)))) 5015 (when (and lsp-warn-no-matched-clients 5016 (null configured-language)) 5017 (lsp-warn "Unable to calculate the languageId for buffer `%s'. \ 5018 Take a look at `lsp-language-id-configuration'. The `major-mode' is %s" 5019 (buffer-name) 5020 major-mode)) 5021 lsp--buffer-language))) 5022 5023 (defun lsp-activate-on (&rest languages) 5024 "Returns language activation function. 5025 The function will return t when the `lsp-buffer-language' returns 5026 one of the LANGUAGES." 5027 (lambda (_file-name _mode) 5028 (-contains? languages (lsp-buffer-language)))) 5029 5030 (defun lsp-workspace-root (&optional path) 5031 "Find the workspace root for the current file or PATH." 5032 (-when-let* ((file-name (or path (buffer-file-name))) 5033 (file-name (lsp-f-canonical file-name))) 5034 (->> (lsp-session) 5035 (lsp-session-folders) 5036 (--filter (and (lsp--files-same-host it file-name) 5037 (or (lsp-f-ancestor-of? it file-name) 5038 (equal it file-name)))) 5039 (--max-by (> (length it) (length other)))))) 5040 5041 (defun lsp-on-revert () 5042 "Executed when a file is reverted. 5043 Added to `after-revert-hook'." 5044 (let ((n (buffer-size)) 5045 (revert-buffer-in-progress-p nil)) 5046 (lsp-on-change 0 n n))) 5047 5048 (defun lsp--text-document-did-close (&optional keep-workspace-alive) 5049 "Executed when the file is closed, added to `kill-buffer-hook'. 5050 5051 If KEEP-WORKSPACE-ALIVE is non-nil, do not shutdown the workspace 5052 if it's closing the last buffer in the workspace." 5053 (lsp-foreach-workspace 5054 (cl-callf2 delq (lsp-current-buffer) (lsp--workspace-buffers lsp--cur-workspace)) 5055 (with-demoted-errors "Error sending didClose notification in ‘lsp--text-document-did-close’: %S" 5056 (lsp-notify "textDocument/didClose" 5057 `(:textDocument ,(lsp--text-document-identifier)))) 5058 (when (and (not lsp-keep-workspace-alive) 5059 (not keep-workspace-alive) 5060 (not (lsp--workspace-buffers lsp--cur-workspace))) 5061 (lsp--shutdown-workspace)))) 5062 5063 (defun lsp--will-save-text-document-params (reason) 5064 (list :textDocument (lsp--text-document-identifier) 5065 :reason reason)) 5066 5067 (defun lsp--before-save () 5068 "Before save handler." 5069 (with-demoted-errors "Error in ‘lsp--before-save’: %S" 5070 (let ((params (lsp--will-save-text-document-params 1))) 5071 (when (lsp--send-will-save-p) 5072 (lsp-notify "textDocument/willSave" params)) 5073 (when (and (lsp--send-will-save-wait-until-p) lsp-before-save-edits) 5074 (let ((lsp-response-timeout 0.1)) 5075 (condition-case nil 5076 (lsp--apply-text-edits 5077 (lsp-request "textDocument/willSaveWaitUntil" 5078 params) 5079 'before-save) 5080 (error))))))) 5081 5082 (defun lsp--on-auto-save () 5083 "Handler for auto-save." 5084 (when (lsp--send-will-save-p) 5085 (with-demoted-errors "Error in ‘lsp--on-auto-save’: %S" 5086 (lsp-notify "textDocument/willSave" (lsp--will-save-text-document-params 2))))) 5087 5088 (defun lsp--text-document-did-save () 5089 "Executed when the file is closed, added to `after-save-hook''." 5090 (when (lsp--send-did-save-p) 5091 (with-demoted-errors "Error on ‘lsp--text-document-did-save: %S’" 5092 (lsp-notify "textDocument/didSave" 5093 `( :textDocument ,(lsp--versioned-text-document-identifier) 5094 ,@(when (lsp--save-include-text-p) 5095 (list :text (lsp--buffer-content)))))))) 5096 5097 (defun lsp--text-document-position-params (&optional identifier position) 5098 "Make TextDocumentPositionParams for the current point in the current document. 5099 If IDENTIFIER and POSITION are non-nil, they will be used as the document 5100 identifier and the position respectively." 5101 (list :textDocument (or identifier (lsp--text-document-identifier)) 5102 :position (or position (lsp--cur-position)))) 5103 5104 (defun lsp--get-buffer-diagnostics () 5105 "Return buffer diagnostics." 5106 (gethash (or 5107 (plist-get lsp--virtual-buffer :buffer-file-name) 5108 (lsp--fix-path-casing (buffer-file-name))) 5109 (lsp-diagnostics t))) 5110 5111 (defun lsp-cur-line-diagnostics () 5112 "Return any diagnostics that apply to the current line." 5113 (-let [(&plist :start (&plist :line start) :end (&plist :line end)) (lsp--region-or-line)] 5114 (cl-coerce (-filter 5115 (-lambda ((&Diagnostic :range (&Range :start (&Position :line)))) 5116 (and (>= line start) (<= line end))) 5117 (lsp--get-buffer-diagnostics)) 5118 'vector))) 5119 5120 (lsp-defun lsp-range-overlapping?((left &as &Range :start left-start :end left-end) 5121 (right &as &Range :start right-start :end right-end)) 5122 (or (lsp-point-in-range? right-start left) 5123 (lsp-point-in-range? right-end left) 5124 (lsp-point-in-range? left-start right) 5125 (lsp-point-in-range? left-end right))) 5126 5127 (defun lsp-make-position-1 (position) 5128 (lsp-make-position :line (plist-get position :line) 5129 :character (plist-get position :character))) 5130 5131 (defun lsp-cur-possition-diagnostics () 5132 "Return any diagnostics that apply to the current line." 5133 (-let* ((start (if (use-region-p) (region-beginning) (point))) 5134 (end (if (use-region-p) (region-end) (point))) 5135 (current-range (lsp-make-range :start (lsp-make-position-1 (lsp-point-to-position start)) 5136 :end (lsp-make-position-1 (lsp-point-to-position end))))) 5137 (->> (lsp--get-buffer-diagnostics) 5138 (-filter 5139 (-lambda ((&Diagnostic :range)) 5140 (lsp-range-overlapping? range current-range))) 5141 (apply 'vector)))) 5142 5143 (defalias 'lsp--cur-line-diagnotics 'lsp-cur-line-diagnostics) 5144 5145 (defun lsp--extract-line-from-buffer (pos) 5146 "Return the line pointed to by POS (a Position object) in the current buffer." 5147 (let* ((point (lsp--position-to-point pos)) 5148 (inhibit-field-text-motion t)) 5149 (save-excursion 5150 (goto-char point) 5151 (buffer-substring (line-beginning-position) (line-end-position))))) 5152 5153 (lsp-defun lsp--xref-make-item (filename (&Range :start (start &as &Position :character start-char :line start-line) 5154 :end (end &as &Position :character end-char))) 5155 "Return a xref-item from a RANGE in FILENAME." 5156 (let* ((line (lsp--extract-line-from-buffer start)) 5157 (len (length line))) 5158 (add-face-text-property (max (min start-char len) 0) 5159 (max (min end-char len) 0) 5160 'xref-match t line) 5161 ;; LINE is nil when FILENAME is not being current visited by any buffer. 5162 (xref-make (or line filename) 5163 (xref-make-file-location 5164 filename 5165 (lsp-translate-line (1+ start-line)) 5166 (lsp-translate-column start-char))))) 5167 5168 (defun lsp--location-uri (loc) 5169 (if (lsp-location? loc) 5170 (lsp:location-uri loc) 5171 (lsp:location-link-target-uri loc))) 5172 5173 (lsp-defun lsp-goto-location ((loc &as &Location :uri :range (&Range :start))) 5174 "Go to location." 5175 (let ((path (lsp--uri-to-path uri))) 5176 (if (f-exists? path) 5177 (with-current-buffer (find-file path) 5178 (goto-char (lsp--position-to-point start))) 5179 (error "There is no file %s" path)))) 5180 5181 (defun lsp--location-range (loc) 5182 (if (lsp-location? loc) 5183 (lsp:location-range loc) 5184 (lsp:location-link-target-selection-range loc))) 5185 5186 (defun lsp--locations-to-xref-items (locations) 5187 "Return a list of `xref-item' given LOCATIONS, which can be of 5188 type Location, LocationLink, Location[] or LocationLink[]." 5189 (setq locations 5190 (pcase locations 5191 ((seq (or (Location) 5192 (LocationLink))) 5193 (append locations nil)) 5194 ((or (Location) 5195 (LocationLink)) 5196 (list locations)))) 5197 5198 (cl-labels ((get-xrefs-in-file 5199 (file-locs) 5200 (-let [(filename . matches) file-locs] 5201 (condition-case err 5202 (let ((visiting (find-buffer-visiting filename)) 5203 (fn (lambda (loc) 5204 (lsp-with-filename filename 5205 (lsp--xref-make-item filename 5206 (lsp--location-range loc)))))) 5207 (if visiting 5208 (with-current-buffer visiting 5209 (seq-map fn matches)) 5210 (when (file-readable-p filename) 5211 (with-temp-buffer 5212 (insert-file-contents-literally filename) 5213 (seq-map fn matches))))) 5214 (error (lsp-warn "Failed to process xref entry for filename '%s': %s" 5215 filename (error-message-string err))) 5216 (file-error (lsp-warn "Failed to process xref entry, file-error, '%s': %s" 5217 filename (error-message-string err))))))) 5218 5219 (->> locations 5220 (seq-sort #'lsp--location-before-p) 5221 (seq-group-by (-compose #'lsp--uri-to-path #'lsp--location-uri)) 5222 (seq-map #'get-xrefs-in-file) 5223 (apply #'nconc)))) 5224 5225 (defun lsp--location-before-p (left right) 5226 "Sort first by file, then by line, then by column." 5227 (let ((left-uri (lsp--location-uri left)) 5228 (right-uri (lsp--location-uri right))) 5229 (if (not (string= left-uri right-uri)) 5230 (string< left-uri right-uri) 5231 (-let (((&Range :start left-start) (lsp--location-range left)) 5232 ((&Range :start right-start) (lsp--location-range right))) 5233 (lsp--position-compare right-start left-start))))) 5234 5235 (defun lsp--make-reference-params (&optional td-position exclude-declaration) 5236 "Make a ReferenceParam object. 5237 If TD-POSITION is non-nil, use it as TextDocumentPositionParams object instead. 5238 If EXCLUDE-DECLARATION is non-nil, request the server to include declarations." 5239 (let ((json-false :json-false)) 5240 (plist-put (or td-position (lsp--text-document-position-params)) 5241 :context `(:includeDeclaration ,(lsp-json-bool (not exclude-declaration)))))) 5242 5243 (defun lsp--cancel-request (id) 5244 "Cancel request with ID in all workspaces." 5245 (lsp-foreach-workspace 5246 (->> lsp--cur-workspace lsp--workspace-client lsp--client-response-handlers (remhash id)) 5247 (lsp-notify "$/cancelRequest" `(:id ,id)))) 5248 5249 (defvar-local lsp--hover-saved-bounds nil) 5250 5251 (defun lsp-eldoc-function (cb &rest _ignored) 5252 "`lsp-mode' eldoc function to display hover info (based on `textDocument/hover')." 5253 (if (and lsp--hover-saved-bounds 5254 (lsp--point-in-bounds-p lsp--hover-saved-bounds)) 5255 lsp--eldoc-saved-message 5256 (setq lsp--hover-saved-bounds nil 5257 lsp--eldoc-saved-message nil) 5258 (if (looking-at-p "[[:space:]\n]") 5259 (setq lsp--eldoc-saved-message nil) ; And returns nil. 5260 (when (and lsp-eldoc-enable-hover (lsp-feature? "textDocument/hover")) 5261 (lsp-request-async 5262 "textDocument/hover" 5263 (lsp--text-document-position-params) 5264 (-lambda ((hover &as &Hover? :range? :contents)) 5265 (setq lsp--hover-saved-bounds (when range? 5266 (lsp--range-to-region range?))) 5267 (funcall cb (setq lsp--eldoc-saved-message 5268 (when contents 5269 (lsp--render-on-hover-content 5270 contents 5271 lsp-eldoc-render-all))))) 5272 :error-handler #'ignore 5273 :mode 'tick 5274 :cancel-token :eldoc-hover))))) 5275 5276 (defun lsp--point-on-highlight? () 5277 (-some? (lambda (overlay) 5278 (overlay-get overlay 'lsp-highlight)) 5279 (overlays-at (point)))) 5280 5281 (defun lsp--cleanup-highlights-if-needed () 5282 (when (and lsp-enable-symbol-highlighting 5283 lsp--have-document-highlights 5284 (not (lsp--point-on-highlight?))) 5285 (lsp--remove-overlays 'lsp-highlight) 5286 (setq lsp--have-document-highlights nil) 5287 (lsp-cancel-request-by-token :highlights))) 5288 5289 (defvar-local lsp--symbol-bounds-of-last-highlight-invocation nil 5290 "The bounds of the symbol from which `lsp--document-highlight' 5291 most recently requested highlights.") 5292 5293 (defun lsp--document-highlight () 5294 (when (lsp-feature? "textDocument/documentHighlight") 5295 (let ((curr-sym-bounds (bounds-of-thing-at-point 'symbol))) 5296 (unless (or (looking-at-p "[[:space:]\n]") 5297 (not lsp-enable-symbol-highlighting) 5298 (and lsp--have-document-highlights 5299 curr-sym-bounds 5300 (equal curr-sym-bounds 5301 lsp--symbol-bounds-of-last-highlight-invocation))) 5302 (setq lsp--symbol-bounds-of-last-highlight-invocation 5303 curr-sym-bounds) 5304 (lsp-request-async "textDocument/documentHighlight" 5305 (lsp--text-document-position-params) 5306 #'lsp--document-highlight-callback 5307 :mode 'tick 5308 :cancel-token :highlights))))) 5309 5310 (defun lsp--help-open-link (&rest _) 5311 "Open markdown link at point via mouse or keyboard." 5312 (interactive "P") 5313 (let ((buffer-list-update-hook nil)) 5314 (-let [(buffer point) (if-let* ((valid (and (listp last-input-event) 5315 (eq (car last-input-event) 'mouse-2))) 5316 (event (cadr last-input-event)) 5317 (win (posn-window event)) 5318 (buffer (window-buffer win))) 5319 `(,buffer ,(posn-point event)) 5320 `(,(current-buffer) ,(point)))] 5321 (with-current-buffer buffer 5322 (when-let* ((face (get-text-property point 'face)) 5323 (url (or (and (eq face 'markdown-link-face) 5324 (get-text-property point 'help-echo)) 5325 (and (memq face '(markdown-url-face markdown-plain-url-face)) 5326 (nth 3 (markdown-link-at-pos point)))))) 5327 (lsp--document-link-handle-target url)))))) 5328 5329 (defvar lsp-help-mode-map 5330 (-doto (make-sparse-keymap) 5331 (define-key [remap markdown-follow-link-at-point] #'lsp--help-open-link)) 5332 "Keymap for `lsp-help-mode'.") 5333 5334 (define-derived-mode lsp-help-mode help-mode "LspHelp" 5335 "Major mode for displaying lsp help.") 5336 5337 (defun lsp-describe-thing-at-point () 5338 "Display the type signature and documentation of the thing at point." 5339 (interactive) 5340 (let ((contents (-some->> (lsp--text-document-position-params) 5341 (lsp--make-request "textDocument/hover") 5342 (lsp--send-request) 5343 (lsp:hover-contents)))) 5344 (if (and contents (not (equal contents ""))) 5345 (let ((lsp-help-buf-name "*lsp-help*")) 5346 (with-current-buffer (get-buffer-create lsp-help-buf-name) 5347 (delay-mode-hooks 5348 (lsp-help-mode) 5349 (with-help-window lsp-help-buf-name 5350 (insert (string-trim-right (lsp--render-on-hover-content contents t))))) 5351 (run-mode-hooks))) 5352 (lsp--info "No content at point.")))) 5353 5354 (defun lsp--point-in-bounds-p (bounds) 5355 "Return whether the current point is within BOUNDS." 5356 (and (<= (car bounds) (point)) (< (point) (cdr bounds)))) 5357 5358 (defun lsp-get-renderer (language) 5359 "Get renderer for LANGUAGE." 5360 (lambda (str) 5361 (lsp--render-string str language))) 5362 5363 (defun lsp--setup-markdown (mode) 5364 "Setup the ‘markdown-mode’ in the frame. 5365 MODE is the mode used in the parent frame." 5366 (make-local-variable 'markdown-code-lang-modes) 5367 (dolist (mark (alist-get mode lsp-custom-markup-modes)) 5368 (add-to-list 'markdown-code-lang-modes (cons mark mode))) 5369 (setq-local markdown-fontify-code-blocks-natively t) 5370 (setq-local markdown-fontify-code-block-default-mode mode) 5371 (setq-local markdown-hide-markup t) 5372 5373 ;; Render some common HTML entities. 5374 ;; This should really happen in markdown-mode instead, 5375 ;; but it doesn't, so we do it here for now. 5376 (setq prettify-symbols-alist 5377 (cl-loop for i from 0 to 255 5378 collect (cons (format "&#x%02X;" i) i))) 5379 (push '("<" . ?<) prettify-symbols-alist) 5380 (push '(">" . ?>) prettify-symbols-alist) 5381 (push '("&" . ?&) prettify-symbols-alist) 5382 (push '(" " . ? ) prettify-symbols-alist) 5383 (setq prettify-symbols-compose-predicate 5384 (lambda (_start _end _match) t)) 5385 (prettify-symbols-mode 1)) 5386 5387 (defvar lsp-help-link-keymap 5388 (let ((map (make-sparse-keymap))) 5389 (define-key map [mouse-2] #'lsp--help-open-link) 5390 (define-key map "\r" #'lsp--help-open-link) 5391 map) 5392 "Keymap active on links in *lsp-help* mode.") 5393 5394 (defun lsp--fix-markdown-links () 5395 (let ((inhibit-read-only t) 5396 (inhibit-modification-hooks t) 5397 (prop)) 5398 (save-restriction 5399 (goto-char (point-min)) 5400 (while (setq prop (markdown-find-next-prop 'face)) 5401 (let ((end (or (next-single-property-change (car prop) 'face) 5402 (point-max)))) 5403 (when (memq (get-text-property (car prop) 'face) 5404 '(markdown-link-face 5405 markdown-url-face 5406 markdown-plain-url-face)) 5407 (add-text-properties (car prop) end 5408 (list 'button t 5409 'category 'lsp-help-link 5410 'follow-link t 5411 'keymap lsp-help-link-keymap))) 5412 (goto-char end)))))) 5413 5414 (defun lsp--buffer-string-visible () 5415 "Return visible buffer string. 5416 Stolen from `org-copy-visible'." 5417 (let ((temp (generate-new-buffer " *temp*")) 5418 (beg (point-min)) 5419 (end (point-max))) 5420 (while (/= beg end) 5421 (when (get-char-property beg 'invisible) 5422 (setq beg (next-single-char-property-change beg 'invisible nil end))) 5423 (let* ((next (next-single-char-property-change beg 'invisible nil end)) 5424 (substring (buffer-substring beg next))) 5425 (with-current-buffer temp (insert substring)) 5426 ;; (setq result (concat result substring)) 5427 (setq beg next))) 5428 (setq deactivate-mark t) 5429 (prog1 (with-current-buffer temp 5430 (s-chop-suffix "\n" (buffer-string))) 5431 (kill-buffer temp)))) 5432 5433 (defvar lsp-buffer-major-mode nil 5434 "Holds the major mode when fontification function is running. 5435 See #2588") 5436 5437 (defvar view-inhibit-help-message) 5438 5439 (defun lsp--render-markdown () 5440 "Render markdown." 5441 5442 (let ((markdown-enable-math nil)) 5443 (goto-char (point-min)) 5444 (while (re-search-forward 5445 (rx (and "\\" (group (or "\\" "`" "*" "_" ":" "/" 5446 "{" "}" "[" "]" "(" ")" 5447 "#" "+" "-" "." "!" "|")))) 5448 nil t) 5449 (replace-match (rx (backref 1)))) 5450 5451 ;; markdown-mode v2.3 does not yet provide gfm-view-mode 5452 (if (fboundp 'gfm-view-mode) 5453 (let ((view-inhibit-help-message t)) 5454 (gfm-view-mode)) 5455 (gfm-mode)) 5456 5457 (lsp--setup-markdown lsp-buffer-major-mode))) 5458 5459 (defvar lsp--display-inline-image-alist 5460 '((lsp--render-markdown 5461 (:regexp 5462 "!\\[.*?\\](data:image/[a-zA-Z]+;base64,\\([A-Za-z0-9+/\n]+?=*?\\)\\(|[^)]+\\)?)" 5463 :sexp 5464 (create-image 5465 (base64-decode-string 5466 (buffer-substring-no-properties (match-beginning 1) (match-end 1))) 5467 nil t)))) 5468 "Replaced string regexp and function returning image. 5469 Each element should have the form (MODE . (PROPERTY-LIST...)). 5470 MODE (car) is function which is defined in `lsp-language-id-configuration'. 5471 Cdr should be list of PROPERTY-LIST. 5472 5473 Each PROPERTY-LIST should have properties: 5474 :regexp Regexp which determines what string is relpaced to image. 5475 You should also get information of image, by parenthesis constructs. 5476 By default, all matched string is replaced to image, but you can 5477 change index of replaced string by keyword :replaced-index. 5478 5479 :sexp Return image when evaluated. You can use information of regexp 5480 by using (match-beggining N), (match-end N) or (match-substring N). 5481 5482 In addition, each can have property: 5483 :replaced-index Determine index which is used to replace regexp to image. 5484 The value means first argument of `match-beginning' and 5485 `match-end'. If omitted, interpreted as index 0.") 5486 5487 (defcustom lsp-display-inline-image t 5488 "Showing inline image or not." 5489 :group 'lsp-mode 5490 :type 'boolean) 5491 5492 (defcustom lsp-enable-suggest-server-download t 5493 "When non-nil enable server downloading suggestions." 5494 :group 'lsp-mode 5495 :type 'boolean 5496 :package-version '(lsp-mode . "9.0.0")) 5497 5498 (defcustom lsp-auto-register-remote-clients t 5499 "When non-nil register remote when registering the local one." 5500 :group 'lsp-mode 5501 :type 'boolean 5502 :package-version '(lsp-mode . "9.0.0")) 5503 5504 (defun lsp--display-inline-image (mode) 5505 "Add image property if available." 5506 (let ((plist-list (cdr (assq mode lsp--display-inline-image-alist)))) 5507 (when (and (display-images-p) lsp-display-inline-image) 5508 (cl-loop 5509 for plist in plist-list 5510 with regexp with replaced-index 5511 do 5512 (setq regexp (plist-get plist :regexp)) 5513 (setq replaced-index (or (plist-get plist :replaced-index) 0)) 5514 5515 (font-lock-remove-keywords nil (list regexp replaced-index)) 5516 (let ((inhibit-read-only t)) 5517 (save-excursion 5518 (goto-char (point-min)) 5519 (while (re-search-forward regexp nil t) 5520 (set-text-properties 5521 (match-beginning replaced-index) (match-end replaced-index) 5522 nil) 5523 (add-text-properties 5524 (match-beginning replaced-index) (match-end replaced-index) 5525 `(display ,(eval (plist-get plist :sexp))))))))))) 5526 5527 (defun lsp--fontlock-with-mode (str mode) 5528 "Fontlock STR with MODE." 5529 (let ((lsp-buffer-major-mode major-mode)) 5530 (with-temp-buffer 5531 (with-demoted-errors "Error during doc rendering: %s" 5532 (insert str) 5533 (delay-mode-hooks (funcall mode)) 5534 (cl-flet ((window-body-width () lsp-window-body-width)) 5535 ;; This can go wrong in some cases, and the fontification would 5536 ;; not work as expected. 5537 ;; 5538 ;; See #2984 5539 (ignore-errors (font-lock-ensure)) 5540 (lsp--display-inline-image mode) 5541 (when (eq mode 'lsp--render-markdown) 5542 (lsp--fix-markdown-links)))) 5543 (lsp--buffer-string-visible)))) 5544 5545 (defun lsp--render-string (str language) 5546 "Render STR using `major-mode' corresponding to LANGUAGE. 5547 When language is nil render as markup if `markdown-mode' is loaded." 5548 (setq str (s-replace "\r" "" (or str ""))) 5549 (if-let* ((modes (-keep (-lambda ((mode . lang)) 5550 (when (and (equal lang language) (functionp mode)) 5551 mode)) 5552 lsp-language-id-configuration)) 5553 (mode (car (or (member major-mode modes) modes)))) 5554 (lsp--fontlock-with-mode str mode) 5555 str)) 5556 5557 (defun lsp--render-element (content) 5558 "Render CONTENT element." 5559 (let ((inhibit-message t)) 5560 (or 5561 (pcase content 5562 ((MarkedString :value :language) 5563 (lsp--render-string value language)) 5564 ((MarkupContent :value :kind) 5565 (lsp--render-string value kind)) 5566 ;; plain string 5567 ((pred stringp) (lsp--render-string content "markdown")) 5568 ((pred null) "") 5569 (_ (error "Failed to handle %s" content))) 5570 ""))) 5571 5572 (defun lsp--create-unique-string-fn () 5573 (let (elements) 5574 (lambda (element) 5575 (let ((count (cl-count element elements :test #'string=))) 5576 (prog1 (if (zerop count) 5577 element 5578 (format "%s (%s)" element count)) 5579 (push element elements)))))) 5580 5581 (defun lsp--select-action (actions) 5582 "Select an action to execute from ACTIONS." 5583 (cond 5584 ((seq-empty-p actions) (signal 'lsp-no-code-actions nil)) 5585 ((and (eq (seq-length actions) 1) lsp-auto-execute-action) 5586 (lsp-seq-first actions)) 5587 (t (let ((completion-ignore-case t)) 5588 (lsp--completing-read "Select code action: " 5589 (seq-into actions 'list) 5590 (-compose (lsp--create-unique-string-fn) 5591 #'lsp:code-action-title) 5592 nil t))))) 5593 5594 (defun lsp--workspace-server-id (workspace) 5595 "Return the server ID of WORKSPACE." 5596 (-> workspace lsp--workspace-client lsp--client-server-id)) 5597 5598 (defun lsp--handle-rendered-for-echo-area (contents) 5599 "Return a single line from RENDERED, appropriate for display in the echo area." 5600 (pcase (lsp-workspaces) 5601 (`(,workspace) 5602 (lsp-clients-extract-signature-on-hover contents (lsp--workspace-server-id workspace))) 5603 ;; For projects with multiple active workspaces we also default to 5604 ;; render the first line. 5605 (_ (lsp-clients-extract-signature-on-hover contents nil)))) 5606 5607 (cl-defmethod lsp-clients-extract-signature-on-hover (contents _server-id) 5608 "Extract a representative line from CONTENTS, to show in the echo area." 5609 (car (s-lines (s-trim (lsp--render-element contents))))) 5610 5611 (defun lsp--render-on-hover-content (contents render-all) 5612 "Render the content received from `document/onHover' request. 5613 CONTENTS - MarkedString | MarkedString[] | MarkupContent 5614 RENDER-ALL - nil if only the signature should be rendered." 5615 (cond 5616 ((lsp-markup-content? contents) 5617 ;; MarkupContent. 5618 ;; It tends to be long and is not suitable to display fully in the echo area. 5619 ;; Just display the first line which is typically the signature. 5620 (if render-all 5621 (lsp--render-element contents) 5622 (lsp--handle-rendered-for-echo-area contents))) 5623 ((and (stringp contents) (not (string-match-p "\n" contents))) 5624 ;; If the contents is a single string containing a single line, 5625 ;; render it always. 5626 (lsp--render-element contents)) 5627 (t 5628 ;; MarkedString -> MarkedString[] 5629 (when (or (lsp-marked-string? contents) (stringp contents)) 5630 (setq contents (list contents))) 5631 ;; Consider the signature consisting of the elements who have a renderable 5632 ;; "language" property. When render-all is nil, ignore other elements. 5633 (string-join 5634 (seq-map 5635 #'lsp--render-element 5636 (if render-all 5637 contents 5638 ;; Only render contents that have an available renderer. 5639 (seq-take 5640 (seq-filter 5641 (-andfn #'lsp-marked-string? 5642 (-compose #'lsp-get-renderer #'lsp:marked-string-language)) 5643 contents) 5644 1))) 5645 (if (bound-and-true-p page-break-lines-mode) 5646 "\n\n" 5647 "\n"))))) 5648 5649 5650 5651 (defvar lsp-signature-mode-map 5652 (-doto (make-sparse-keymap) 5653 (define-key (kbd "M-n") #'lsp-signature-next) 5654 (define-key (kbd "M-p") #'lsp-signature-previous) 5655 (define-key (kbd "M-a") #'lsp-signature-toggle-full-docs) 5656 (define-key (kbd "C-c C-k") #'lsp-signature-stop) 5657 (define-key (kbd "C-g") #'lsp-signature-stop)) 5658 "Keymap for `lsp-signature-mode'.") 5659 5660 (define-minor-mode lsp-signature-mode 5661 "Mode used to show signature popup." 5662 :keymap lsp-signature-mode-map 5663 :lighter "" 5664 :group 'lsp-mode) 5665 5666 (defun lsp-signature-stop () 5667 "Stop showing current signature help." 5668 (interactive) 5669 (lsp-cancel-request-by-token :signature) 5670 (remove-hook 'post-command-hook #'lsp-signature) 5671 (funcall lsp-signature-function nil) 5672 (lsp-signature-mode -1)) 5673 5674 (declare-function page-break-lines--update-display-tables "ext:page-break-lines") 5675 5676 (defun lsp--setup-page-break-mode-if-present () 5677 "Enable `page-break-lines-mode' in current buffer." 5678 (when (fboundp 'page-break-lines-mode) 5679 (page-break-lines-mode) 5680 ;; force page-break-lines-mode to update the display tables. 5681 (page-break-lines--update-display-tables))) 5682 5683 (defun lsp-lv-message (message) 5684 (add-hook 'lv-window-hook #'lsp--setup-page-break-mode-if-present) 5685 (if message 5686 (progn 5687 (setq lsp--signature-last-buffer (current-buffer)) 5688 (let ((lv-force-update t)) 5689 (lv-message "%s" message))) 5690 (lv-delete-window) 5691 (remove-hook 'lv-window-hook #'lsp--setup-page-break-mode-if-present))) 5692 5693 (declare-function posframe-show "ext:posframe") 5694 (declare-function posframe-hide "ext:posframe") 5695 (declare-function posframe-poshandler-point-bottom-left-corner-upward "ext:posframe") 5696 5697 (defface lsp-signature-posframe 5698 '((t :inherit tooltip)) 5699 "Background and foreground for `lsp-signature-posframe'." 5700 :group 'lsp-mode) 5701 5702 (defvar lsp-signature-posframe-params 5703 (list :poshandler #'posframe-poshandler-point-bottom-left-corner-upward 5704 :height 10 5705 :width 60 5706 :border-width 1 5707 :min-width 60) 5708 "Params for signature and `posframe-show'.") 5709 5710 (defun lsp-signature-posframe (str) 5711 "Use posframe to show the STR signatureHelp string." 5712 (if str 5713 (apply #'posframe-show 5714 (with-current-buffer (get-buffer-create " *lsp-signature*") 5715 (erase-buffer) 5716 (insert str) 5717 (visual-line-mode 1) 5718 (lsp--setup-page-break-mode-if-present) 5719 (current-buffer)) 5720 (append 5721 lsp-signature-posframe-params 5722 (list :position (point) 5723 :background-color (face-attribute 'lsp-signature-posframe :background nil t) 5724 :foreground-color (face-attribute 'lsp-signature-posframe :foreground nil t) 5725 :border-color (face-attribute 'font-lock-comment-face :foreground nil t)))) 5726 (posframe-hide " *lsp-signature*"))) 5727 5728 (defun lsp--handle-signature-update (signature) 5729 (let ((message 5730 (if (lsp-signature-help? signature) 5731 (lsp--signature->message signature) 5732 (mapconcat #'lsp--signature->message signature "\n")))) 5733 (if (s-present? message) 5734 (funcall lsp-signature-function message) 5735 (lsp-signature-stop)))) 5736 5737 (defun lsp-signature-activate () 5738 "Activate signature help. 5739 It will show up only if current point has signature help." 5740 (interactive) 5741 (setq lsp--signature-last nil 5742 lsp--signature-last-index nil 5743 lsp--signature-last-buffer (current-buffer)) 5744 (add-hook 'post-command-hook #'lsp-signature) 5745 (lsp-signature-mode t)) 5746 5747 (defcustom lsp-signature-cycle t 5748 "Whether `lsp-signature-next' and prev should cycle." 5749 :type 'boolean 5750 :group 'lsp-mode) 5751 5752 (defun lsp-signature-next () 5753 "Show next signature." 5754 (interactive) 5755 (let ((nsigs (length (lsp:signature-help-signatures lsp--signature-last)))) 5756 (when (and lsp--signature-last-index 5757 lsp--signature-last 5758 (or lsp-signature-cycle (< (1+ lsp--signature-last-index) nsigs))) 5759 (setq lsp--signature-last-index (% (1+ lsp--signature-last-index) nsigs)) 5760 (funcall lsp-signature-function (lsp--signature->message lsp--signature-last))))) 5761 5762 (defun lsp-signature-previous () 5763 "Next signature." 5764 (interactive) 5765 (when (and lsp--signature-last-index 5766 lsp--signature-last 5767 (or lsp-signature-cycle (not (zerop lsp--signature-last-index)))) 5768 (setq lsp--signature-last-index (1- (if (zerop lsp--signature-last-index) 5769 (length (lsp:signature-help-signatures lsp--signature-last)) 5770 lsp--signature-last-index))) 5771 (funcall lsp-signature-function (lsp--signature->message lsp--signature-last)))) 5772 5773 (defun lsp-signature-toggle-full-docs () 5774 "Toggle full/partial signature documentation." 5775 (interactive) 5776 (let ((all? (not (numberp lsp-signature-doc-lines)))) 5777 (setq lsp-signature-doc-lines (if all? 5778 (or (car-safe lsp-signature-doc-lines) 5779 20) 5780 (list lsp-signature-doc-lines)))) 5781 (lsp-signature-activate)) 5782 5783 (defun lsp--signature->message (signature-help) 5784 "Generate eldoc message from SIGNATURE-HELP response." 5785 (setq lsp--signature-last signature-help) 5786 5787 (when (and signature-help (not (seq-empty-p (lsp:signature-help-signatures signature-help)))) 5788 (-let* (((&SignatureHelp :active-signature? 5789 :active-parameter? 5790 :signatures) signature-help) 5791 (active-signature? (or lsp--signature-last-index active-signature? 0)) 5792 (_ (setq lsp--signature-last-index active-signature?)) 5793 ((signature &as &SignatureInformation? :label :parameters?) (seq-elt signatures active-signature?)) 5794 (prefix (if (= (length signatures) 1) 5795 "" 5796 (concat (propertize (format " %s/%s" 5797 (1+ active-signature?) 5798 (length signatures)) 5799 'face 'success) 5800 " "))) 5801 (method-docs (when 5802 (and lsp-signature-render-documentation 5803 (or (not (numberp lsp-signature-doc-lines)) (< 0 lsp-signature-doc-lines))) 5804 (let ((docs (lsp--render-element 5805 (lsp:parameter-information-documentation? signature)))) 5806 (when (s-present? docs) 5807 (concat 5808 "\n" 5809 (if (fboundp 'page-break-lines-mode) 5810 "\n" 5811 "") 5812 (if (and (numberp lsp-signature-doc-lines) 5813 (> (length (s-lines docs)) lsp-signature-doc-lines)) 5814 (concat (s-join "\n" (-take lsp-signature-doc-lines (s-lines docs))) 5815 (propertize "\nTruncated..." 'face 'highlight)) 5816 docs))))))) 5817 (when (and active-parameter? (not (seq-empty-p parameters?))) 5818 (-when-let* ((param (when (and (< -1 active-parameter? (length parameters?))) 5819 (seq-elt parameters? active-parameter?))) 5820 (selected-param-label (let ((label (lsp:parameter-information-label param))) 5821 (if (stringp label) label (append label nil)))) 5822 (start (if (stringp selected-param-label) 5823 (s-index-of selected-param-label label) 5824 (cl-first selected-param-label))) 5825 (end (if (stringp selected-param-label) 5826 (+ start (length selected-param-label)) 5827 (cl-second selected-param-label)))) 5828 (add-face-text-property start end 'eldoc-highlight-function-argument nil label))) 5829 (concat prefix label method-docs)))) 5830 5831 (defun lsp-signature () 5832 "Display signature info (based on `textDocument/signatureHelp')" 5833 (if (and lsp--signature-last-buffer 5834 (not (equal (current-buffer) lsp--signature-last-buffer))) 5835 (lsp-signature-stop) 5836 (lsp-request-async "textDocument/signatureHelp" 5837 (lsp--text-document-position-params) 5838 #'lsp--handle-signature-update 5839 :cancel-token :signature))) 5840 5841 5842 (defcustom lsp-overlay-document-color-char "■" 5843 "Display the char represent the document color in overlay" 5844 :type 'string 5845 :group 'lsp-mode) 5846 5847 ;; color presentation 5848 (defun lsp--color-create-interactive-command (color range) 5849 (lambda () 5850 (interactive) 5851 (-let [(&ColorPresentation? :text-edit? 5852 :additional-text-edits?) 5853 (lsp--completing-read 5854 "Select color presentation: " 5855 (lsp-request 5856 "textDocument/colorPresentation" 5857 `( :textDocument ,(lsp--text-document-identifier) 5858 :color ,color 5859 :range ,range)) 5860 #'lsp:color-presentation-label 5861 nil 5862 t)] 5863 (when text-edit? 5864 (lsp--apply-text-edit text-edit?)) 5865 (when additional-text-edits? 5866 (lsp--apply-text-edits additional-text-edits? 'color-presentation))))) 5867 5868 (defun lsp--number->color (number) 5869 (let ((result (format "%x" 5870 (round (* (or number 0) 255.0))))) 5871 (if (= 1 (length result)) 5872 (concat "0" result) 5873 result))) 5874 5875 (defun lsp--document-color () 5876 "Document color handler." 5877 (when (lsp-feature? "textDocument/documentColor") 5878 (lsp-request-async 5879 "textDocument/documentColor" 5880 `(:textDocument ,(lsp--text-document-identifier)) 5881 (lambda (result) 5882 (lsp--remove-overlays 'lsp-color) 5883 (seq-do 5884 (-lambda ((&ColorInformation :color (color &as &Color :red :green :blue) 5885 :range)) 5886 (-let* (((beg . end) (lsp--range-to-region range)) 5887 (overlay (make-overlay beg end)) 5888 (command (lsp--color-create-interactive-command color range))) 5889 (overlay-put overlay 'lsp-color t) 5890 (overlay-put overlay 'evaporate t) 5891 (overlay-put overlay 5892 'before-string 5893 (propertize 5894 lsp-overlay-document-color-char 5895 'face `((:foreground ,(format 5896 "#%s%s%s" 5897 (lsp--number->color red) 5898 (lsp--number->color green) 5899 (lsp--number->color blue)))) 5900 'action command 5901 'mouse-face 'lsp-lens-mouse-face 5902 'local-map (-doto (make-sparse-keymap) 5903 (define-key [mouse-1] command)))))) 5904 result)) 5905 :mode 'unchanged 5906 :cancel-token :document-color-token))) 5907 5908 5909 5910 (defun lsp--action-trigger-parameter-hints (_command) 5911 "Handler for editor.action.triggerParameterHints." 5912 (when (member :on-server-request lsp-signature-auto-activate) 5913 (lsp-signature-activate))) 5914 5915 (defun lsp--action-trigger-suggest (_command) 5916 "Handler for editor.action.triggerSuggest." 5917 (cond 5918 ((and (bound-and-true-p company-mode) 5919 (fboundp 'company-auto-begin) 5920 (fboundp 'company-post-command)) 5921 (run-at-time 0 nil 5922 (lambda () 5923 (let ((this-command 'company-idle-begin) 5924 (company-minimum-prefix-length 0)) 5925 (company-auto-begin) 5926 (company-post-command))))) 5927 (t 5928 (completion-at-point)))) 5929 5930 (defconst lsp--default-action-handlers 5931 (ht ("editor.action.triggerParameterHints" #'lsp--action-trigger-parameter-hints) 5932 ("editor.action.triggerSuggest" #'lsp--action-trigger-suggest)) 5933 "Default action handlers.") 5934 5935 (defun lsp--find-action-handler (command) 5936 "Find action handler for particular COMMAND." 5937 (or 5938 (--some (-some->> it 5939 (lsp--workspace-client) 5940 (lsp--client-action-handlers) 5941 (gethash command)) 5942 (lsp-workspaces)) 5943 (gethash command lsp--default-action-handlers))) 5944 5945 (defun lsp--text-document-code-action-params (&optional kind) 5946 "Code action params." 5947 (list :textDocument (lsp--text-document-identifier) 5948 :range (if (use-region-p) 5949 (lsp--region-to-range (region-beginning) (region-end)) 5950 (lsp--region-to-range (point) (point))) 5951 :context `( :diagnostics ,(lsp-cur-possition-diagnostics) 5952 ,@(when kind (list :only (vector kind)))))) 5953 5954 (defun lsp-code-actions-at-point (&optional kind) 5955 "Retrieve the code actions for the active region or the current line. 5956 It will filter by KIND if non nil." 5957 (lsp-request "textDocument/codeAction" (lsp--text-document-code-action-params kind))) 5958 5959 (defun lsp-execute-code-action-by-kind (command-kind) 5960 "Execute code action by COMMAND-KIND." 5961 (if-let ((action (->> (lsp-get-or-calculate-code-actions command-kind) 5962 (-filter (-lambda ((&CodeAction :kind?)) 5963 (and kind? (s-prefix? command-kind kind?)))) 5964 lsp--select-action))) 5965 (lsp-execute-code-action action) 5966 (signal 'lsp-no-code-actions '(command-kind)))) 5967 5968 (defalias 'lsp-get-or-calculate-code-actions 'lsp-code-actions-at-point) 5969 5970 (lsp-defun lsp--execute-command ((action &as &Command :command :arguments?)) 5971 "Parse and execute a code ACTION represented as a Command LSP type." 5972 (let ((server-id (->> (lsp-workspaces) 5973 (cl-first) 5974 (or lsp--cur-workspace) 5975 (lsp--workspace-client) 5976 (lsp--client-server-id)))) 5977 (condition-case nil 5978 (with-no-warnings 5979 (lsp-execute-command server-id (intern command) arguments?)) 5980 (cl-no-applicable-method 5981 (if-let ((action-handler (lsp--find-action-handler command))) 5982 (funcall action-handler action) 5983 (lsp-send-execute-command command arguments?)))))) 5984 5985 (lsp-defun lsp-execute-code-action ((action &as &CodeAction :command? :edit?)) 5986 "Execute code action ACTION. For example, when text under the 5987 caret has a suggestion to apply a fix from an lsp-server, calling 5988 this function will do so. 5989 If ACTION is not set it will be selected from `lsp-code-actions-at-point'. 5990 Request codeAction/resolve for more info if server supports." 5991 (interactive (list (lsp--select-action (lsp-code-actions-at-point)))) 5992 (if (and (lsp-feature? "codeAction/resolve") 5993 (not command?) 5994 (not edit?)) 5995 (lsp--execute-code-action (lsp-request "codeAction/resolve" action)) 5996 (lsp--execute-code-action action))) 5997 5998 (lsp-defun lsp--execute-code-action ((action &as &CodeAction :command? :edit?)) 5999 "Execute code action ACTION." 6000 (when edit? 6001 (lsp--apply-workspace-edit edit? 'code-action)) 6002 6003 (cond 6004 ((stringp command?) (lsp--execute-command action)) 6005 ((lsp-command? command?) (lsp--execute-command command?)))) 6006 6007 (defvar lsp--formatting-indent-alist 6008 ;; Taken from `dtrt-indent-mode' 6009 '( 6010 (ada-mode . ada-indent) ; Ada 6011 (ada-ts-mode . ada-ts-mode-indent-offset) 6012 (c++-mode . c-basic-offset) ; C++ 6013 (c++-ts-mode . c-ts-mode-indent-offset) 6014 (c-mode . c-basic-offset) ; C 6015 (c-ts-mode . c-ts-mode-indent-offset) 6016 (cperl-mode . cperl-indent-level) ; Perl 6017 (crystal-mode . crystal-indent-level) ; Crystal (Ruby) 6018 (csharp-mode . c-basic-offset) ; C# 6019 (csharp-tree-sitter-mode . csharp-tree-sitter-indent-offset) ; C# 6020 (csharp-ts-mode . csharp-ts-mode-indent-offset) ; C# (tree-sitter, Emacs29) 6021 (css-mode . css-indent-offset) ; CSS 6022 (d-mode . c-basic-offset) ; D 6023 (enh-ruby-mode . enh-ruby-indent-level) ; Ruby 6024 (erlang-mode . erlang-indent-level) ; Erlang 6025 (ess-mode . ess-indent-offset) ; ESS (R) 6026 (go-ts-mode . go-ts-mode-indent-offset) 6027 (gpr-mode . gpr-indent-offset) ; GNAT Project 6028 (gpr-ts-mode . gpr-ts-mode-indent-offset) 6029 (hack-mode . hack-indent-offset) ; Hack 6030 (java-mode . c-basic-offset) ; Java 6031 (java-ts-mode . java-ts-mode-indent-offset) 6032 (jde-mode . c-basic-offset) ; Java (JDE) 6033 (js-mode . js-indent-level) ; JavaScript 6034 (js-ts-mode . js-indent-level) 6035 (js2-mode . js2-basic-offset) ; JavaScript-IDE 6036 (js3-mode . js3-indent-level) ; JavaScript-IDE 6037 (json-mode . js-indent-level) ; JSON 6038 (json-ts-mode . json-ts-mode-indent-offset) 6039 (lua-mode . lua-indent-level) ; Lua 6040 (lua-ts-mode . lua-ts-indent-offset) 6041 (nxml-mode . nxml-child-indent) ; XML 6042 (objc-mode . c-basic-offset) ; Objective C 6043 (pascal-mode . pascal-indent-level) ; Pascal 6044 (perl-mode . perl-indent-level) ; Perl 6045 (php-mode . c-basic-offset) ; PHP 6046 (php-ts-mode . php-ts-mode-indent-offset) ; PHP 6047 (powershell-mode . powershell-indent) ; PowerShell 6048 (powershell-ts-mode . powershell-ts-mode-indent-offset) ; PowerShell 6049 (raku-mode . raku-indent-offset) ; Perl6/Raku 6050 (ruby-mode . ruby-indent-level) ; Ruby 6051 (rust-mode . rust-indent-offset) ; Rust 6052 (rust-ts-mode . rust-ts-mode-indent-offset) 6053 (rustic-mode . rustic-indent-offset) ; Rust 6054 (scala-mode . scala-indent:step) ; Scala 6055 (sgml-mode . sgml-basic-offset) ; SGML 6056 (sh-mode . sh-basic-offset) ; Shell Script 6057 (toml-ts-mode . toml-ts-mode-indent-offset) 6058 (typescript-mode . typescript-indent-level) ; Typescript 6059 (typescript-ts-mode . typescript-ts-mode-indent-offset) ; Typescript (tree-sitter, Emacs29) 6060 (yaml-mode . yaml-indent-offset) ; YAML 6061 (yang-mode . c-basic-offset) ; YANG (yang-mode) 6062 6063 (default . standard-indent)) ; default fallback 6064 "A mapping from `major-mode' to its indent variable.") 6065 6066 (defun lsp--get-indent-width (mode) 6067 "Get indentation offset for MODE." 6068 (or (alist-get mode lsp--formatting-indent-alist) 6069 (lsp--get-indent-width (or (get mode 'derived-mode-parent) 'default)))) 6070 6071 (defun lsp--make-document-formatting-params () 6072 "Create document formatting params." 6073 (lsp-make-document-formatting-params 6074 :text-document (lsp--text-document-identifier) 6075 :options (lsp-make-formatting-options 6076 :tab-size (symbol-value (lsp--get-indent-width major-mode)) 6077 :insert-spaces (lsp-json-bool (not indent-tabs-mode)) 6078 :trim-trailing-whitespace? (lsp-json-bool lsp-trim-trailing-whitespace) 6079 :insert-final-newline? (lsp-json-bool lsp-insert-final-newline) 6080 :trim-final-newlines? (lsp-json-bool lsp-trim-final-newlines)))) 6081 6082 (defun lsp-format-buffer () 6083 "Ask the server to format this document." 6084 (interactive "*") 6085 (cond ((lsp-feature? "textDocument/formatting") 6086 (let ((edits (lsp-request "textDocument/formatting" 6087 (lsp--make-document-formatting-params)))) 6088 (if (seq-empty-p edits) 6089 (lsp--info "No formatting changes provided") 6090 (lsp--apply-text-edits edits 'format)))) 6091 ((lsp-feature? "textDocument/rangeFormatting") 6092 (save-restriction 6093 (widen) 6094 (lsp-format-region (point-min) (point-max)))) 6095 (t (signal 'lsp-capability-not-supported (list "documentFormattingProvider"))))) 6096 6097 (defun lsp-format-region (s e) 6098 "Ask the server to format the region, or if none is selected, the current line." 6099 (interactive "r") 6100 (let ((edits (lsp-request 6101 "textDocument/rangeFormatting" 6102 (lsp--make-document-range-formatting-params s e)))) 6103 (if (seq-empty-p edits) 6104 (lsp--info "No formatting changes provided") 6105 (lsp--apply-text-edits edits 'format)))) 6106 6107 (defmacro lsp-make-interactive-code-action (func-name code-action-kind) 6108 "Define an interactive function FUNC-NAME that attempts to 6109 execute a CODE-ACTION-KIND action." 6110 `(defun ,(intern (concat "lsp-" (symbol-name func-name))) () 6111 ,(format "Perform the %s code action, if available." code-action-kind) 6112 (interactive) 6113 ;; Even when `lsp-auto-execute-action' is nil, it still makes sense to 6114 ;; auto-execute here: the user has specified exactly what they want. 6115 (let ((lsp-auto-execute-action t)) 6116 (condition-case nil 6117 (lsp-execute-code-action-by-kind ,code-action-kind) 6118 (lsp-no-code-actions 6119 (when (called-interactively-p 'any) 6120 (lsp--info ,(format "%s action not available" code-action-kind)))))))) 6121 6122 (lsp-make-interactive-code-action organize-imports "source.organizeImports") 6123 6124 (defun lsp--make-document-range-formatting-params (start end) 6125 "Make DocumentRangeFormattingParams for selected region." 6126 (lsp:set-document-range-formatting-params-range (lsp--make-document-formatting-params) 6127 (lsp--region-to-range start end))) 6128 6129 (defconst lsp--highlight-kind-face 6130 '((1 . lsp-face-highlight-textual) 6131 (2 . lsp-face-highlight-read) 6132 (3 . lsp-face-highlight-write))) 6133 6134 (defun lsp--remove-overlays (name) 6135 (save-restriction 6136 (widen) 6137 (remove-overlays (point-min) (point-max) name t))) 6138 6139 (defun lsp-document-highlight () 6140 "Highlight all relevant references to the symbol under point." 6141 (interactive) 6142 (lsp--remove-overlays 'lsp-highlight) ;; clear any previous highlights 6143 (setq lsp--have-document-highlights nil 6144 lsp--symbol-bounds-of-last-highlight-invocation nil) 6145 (let ((lsp-enable-symbol-highlighting t)) 6146 (lsp--document-highlight))) 6147 6148 (defun lsp--document-highlight-callback (highlights) 6149 "Create a callback to process the reply of a 6150 `textDocument/documentHighlight' message for the buffer BUF. 6151 A reference is highlighted only if it is visible in a window." 6152 (lsp--remove-overlays 'lsp-highlight) 6153 6154 (let* ((wins-visible-pos (-map (lambda (win) 6155 (cons (1- (line-number-at-pos (window-start win) t)) 6156 (1+ (line-number-at-pos (window-end win) t)))) 6157 (get-buffer-window-list nil nil 'visible)))) 6158 (setq lsp--have-document-highlights t) 6159 (-map 6160 (-lambda ((&DocumentHighlight :range (&Range :start (start &as &Position :line start-line) 6161 :end (end &as &Position :line end-line)) 6162 :kind?)) 6163 (-map 6164 (-lambda ((start-window . end-window)) 6165 ;; Make the overlay only if the reference is visible 6166 (let ((start-point (lsp--position-to-point start)) 6167 (end-point (lsp--position-to-point end))) 6168 (when (and (> (1+ start-line) start-window) 6169 (< (1+ end-line) end-window) 6170 (not (and lsp-symbol-highlighting-skip-current 6171 (<= start-point (point) end-point)))) 6172 (-doto (make-overlay start-point end-point) 6173 (overlay-put 'face (cdr (assq (or kind? 1) lsp--highlight-kind-face))) 6174 (overlay-put 'lsp-highlight t))))) 6175 wins-visible-pos)) 6176 highlights))) 6177 6178 (defcustom lsp-symbol-kinds 6179 '((1 . "File") 6180 (2 . "Module") 6181 (3 . "Namespace") 6182 (4 . "Package") 6183 (5 . "Class") 6184 (6 . "Method") 6185 (7 . "Property") 6186 (8 . "Field") 6187 (9 . "Constructor") 6188 (10 . "Enum") 6189 (11 . "Interface") 6190 (12 . "Function") 6191 (13 . "Variable") 6192 (14 . "Constant") 6193 (15 . "String") 6194 (16 . "Number") 6195 (17 . "Boolean") 6196 (18 . "Array") 6197 (19 . "Object") 6198 (20 . "Key") 6199 (21 . "Null") 6200 (22 . "Enum Member") 6201 (23 . "Struct") 6202 (24 . "Event") 6203 (25 . "Operator") 6204 (26 . "Type Parameter")) 6205 "Alist mapping SymbolKinds to human-readable strings. 6206 Various Symbol objects in the LSP protocol have an integral type, 6207 specifying what they are. This alist maps such type integrals to 6208 readable representations of them. See 6209 `https://microsoft.github.io/language-server-protocol/specifications/specification-current/', 6210 namespace SymbolKind." 6211 :group 'lsp-mode 6212 :type '(alist :key-type integer :value-type string)) 6213 (defalias 'lsp--symbol-kind 'lsp-symbol-kinds) 6214 6215 (lsp-defun lsp--symbol-information-to-xref 6216 ((&SymbolInformation :kind :name 6217 :location (&Location :uri :range (&Range :start 6218 (&Position :line :character))))) 6219 "Return a `xref-item' from SYMBOL information." 6220 (xref-make (format "[%s] %s" (alist-get kind lsp-symbol-kinds) name) 6221 (xref-make-file-location (lsp--uri-to-path uri) 6222 line 6223 character))) 6224 6225 (defun lsp--get-document-symbols () 6226 "Get document symbols. 6227 6228 If the buffer has not been modified since symbols were last 6229 retrieved, simply return the latest result. 6230 6231 Else, if the request was initiated by Imenu updating its menu-bar 6232 entry, perform it asynchronously; i.e., give Imenu the latest 6233 result and then force a refresh when a new one is available. 6234 6235 Else (e.g., due to interactive use of `imenu' or `xref'), 6236 perform the request synchronously." 6237 (if (= (buffer-chars-modified-tick) lsp--document-symbols-tick) 6238 lsp--document-symbols 6239 (let ((method "textDocument/documentSymbol") 6240 (params `(:textDocument ,(lsp--text-document-identifier))) 6241 (tick (buffer-chars-modified-tick))) 6242 (if (not lsp--document-symbols-request-async) 6243 (prog1 6244 (setq lsp--document-symbols (lsp-request method params)) 6245 (setq lsp--document-symbols-tick tick)) 6246 (lsp-request-async method params 6247 (lambda (document-symbols) 6248 (setq lsp--document-symbols document-symbols 6249 lsp--document-symbols-tick tick) 6250 (lsp--imenu-refresh)) 6251 :mode 'alive 6252 :cancel-token :document-symbols) 6253 lsp--document-symbols)))) 6254 6255 (advice-add 'imenu-update-menubar :around 6256 (lambda (oldfun &rest r) 6257 (let ((lsp--document-symbols-request-async t)) 6258 (apply oldfun r)))) 6259 6260 (defun lsp--document-symbols->document-symbols-hierarchy (document-symbols current-position) 6261 "Convert DOCUMENT-SYMBOLS to symbols hierarchy on CURRENT-POSITION." 6262 (-let (((symbol &as &DocumentSymbol? :children?) 6263 (seq-find (-lambda ((&DocumentSymbol :range)) 6264 (lsp-point-in-range? current-position range)) 6265 document-symbols))) 6266 (if children? 6267 (cons symbol (lsp--document-symbols->document-symbols-hierarchy children? current-position)) 6268 (when symbol 6269 (list symbol))))) 6270 6271 (lsp-defun lsp--symbol-information->document-symbol ((&SymbolInformation :name :kind :location :container-name? :deprecated?)) 6272 "Convert a SymbolInformation to a DocumentInformation" 6273 (lsp-make-document-symbol :name name 6274 :kind kind 6275 :range (lsp:location-range location) 6276 :children? nil 6277 :deprecated? deprecated? 6278 :selection-range (lsp:location-range location) 6279 :detail? container-name?)) 6280 6281 (defun lsp--symbols-informations->document-symbols-hierarchy (symbols-informations current-position) 6282 "Convert SYMBOLS-INFORMATIONS to symbols hierarchy on CURRENT-POSITION." 6283 (--> symbols-informations 6284 (-keep (-lambda ((symbol &as &SymbolInformation :location (&Location :range))) 6285 (when (lsp-point-in-range? current-position range) 6286 (lsp--symbol-information->document-symbol symbol))) 6287 it) 6288 (sort it (-lambda ((&DocumentSymbol :range (&Range :start a-start-position :end a-end-position)) 6289 (&DocumentSymbol :range (&Range :start b-start-position :end b-end-position))) 6290 (and (lsp--position-compare b-start-position a-start-position) 6291 (lsp--position-compare a-end-position b-end-position)))))) 6292 6293 (defun lsp--symbols->document-symbols-hierarchy (symbols) 6294 "Convert SYMBOLS to symbols-hierarchy." 6295 (when-let ((first-symbol (lsp-seq-first symbols))) 6296 (let ((cur-position (lsp-make-position :line (plist-get (lsp--cur-position) :line) 6297 :character (plist-get (lsp--cur-position) :character)))) 6298 (if (lsp-symbol-information? first-symbol) 6299 (lsp--symbols-informations->document-symbols-hierarchy symbols cur-position) 6300 (lsp--document-symbols->document-symbols-hierarchy symbols cur-position))))) 6301 6302 (defun lsp--xref-backend () 'xref-lsp) 6303 6304 (cl-defmethod xref-backend-identifier-at-point ((_backend (eql xref-lsp))) 6305 (propertize (or (thing-at-point 'symbol) "") 6306 'identifier-at-point t)) 6307 6308 (defun lsp--xref-elements-index (symbols path) 6309 (-mapcat 6310 (-lambda (sym) 6311 (pcase-exhaustive sym 6312 ((DocumentSymbol :name :children? :selection-range (Range :start)) 6313 (cons (cons (concat path name) 6314 (lsp--position-to-point start)) 6315 (lsp--xref-elements-index children? (concat path name " / ")))) 6316 ((SymbolInformation :name :location (Location :range (Range :start))) 6317 (list (cons (concat path name) 6318 (lsp--position-to-point start)))))) 6319 symbols)) 6320 6321 (defvar-local lsp--symbols-cache nil) 6322 6323 (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql xref-lsp))) 6324 (if (lsp--find-workspaces-for "textDocument/documentSymbol") 6325 (progn 6326 (setq lsp--symbols-cache (lsp--xref-elements-index 6327 (lsp--get-document-symbols) nil)) 6328 lsp--symbols-cache) 6329 (list (propertize (or (thing-at-point 'symbol) "") 6330 'identifier-at-point t)))) 6331 6332 (cl-defmethod xref-backend-definitions ((_backend (eql xref-lsp)) identifier) 6333 (save-excursion 6334 (unless (get-text-property 0 'identifier-at-point identifier) 6335 (goto-char (cl-rest (or (assoc identifier lsp--symbols-cache) 6336 (user-error "Unable to find symbol %s in current document" identifier))))) 6337 (lsp--locations-to-xref-items (lsp-request "textDocument/definition" 6338 (lsp--text-document-position-params))))) 6339 6340 (cl-defmethod xref-backend-references ((_backend (eql xref-lsp)) identifier) 6341 (save-excursion 6342 (unless (get-text-property 0 'identifier-at-point identifier) 6343 (goto-char (cl-rest (or (assoc identifier lsp--symbols-cache) 6344 (user-error "Unable to find symbol %s" identifier))))) 6345 (lsp--locations-to-xref-items (lsp-request "textDocument/references" 6346 (lsp--make-reference-params nil lsp-references-exclude-definition))))) 6347 6348 (cl-defmethod xref-backend-apropos ((_backend (eql xref-lsp)) pattern) 6349 (seq-map #'lsp--symbol-information-to-xref 6350 (lsp-request "workspace/symbol" `(:query ,pattern)))) 6351 6352 (defcustom lsp-rename-use-prepare t 6353 "Whether `lsp-rename' should do a prepareRename first. 6354 For some language servers, textDocument/prepareRename might be 6355 too slow, in which case this variable may be set to nil. 6356 `lsp-rename' will then use `thing-at-point' `symbol' to determine 6357 the symbol to rename at point." 6358 :group 'lsp-mode 6359 :type 'boolean) 6360 6361 (defun lsp--get-symbol-to-rename () 6362 "Get a symbol to rename and placeholder at point. 6363 Returns a cons ((START . END) . PLACEHOLDER?), and nil if 6364 renaming is generally supported but cannot be done at point. 6365 START and END are the bounds of the identifiers being renamed, 6366 while PLACEHOLDER?, is either nil or a string suggested by the 6367 language server as the initial input of a new-name prompt." 6368 (unless (lsp-feature? "textDocument/rename") 6369 (error "The connected server(s) doesn't support renaming")) 6370 (if (and lsp-rename-use-prepare (lsp-feature? "textDocument/prepareRename")) 6371 (when-let ((response 6372 (lsp-request "textDocument/prepareRename" 6373 (lsp--text-document-position-params)))) 6374 (let* ((bounds (lsp--range-to-region 6375 (if (lsp-range? response) 6376 response 6377 (lsp:prepare-rename-result-range response)))) 6378 (placeholder 6379 (and (not (lsp-range? response)) 6380 (lsp:prepare-rename-result-placeholder response)))) 6381 (cons bounds placeholder))) 6382 (when-let ((bounds (bounds-of-thing-at-point 'symbol))) 6383 (cons bounds nil)))) 6384 6385 (defface lsp-face-rename '((t :underline t)) 6386 "Face used to highlight the identifier being renamed. 6387 Renaming can be done using `lsp-rename'." 6388 :group 'lsp-mode) 6389 6390 (defface lsp-rename-placeholder-face '((t :inherit font-lock-variable-name-face)) 6391 "Face used to display the rename placeholder in. 6392 When calling `lsp-rename' interactively, this will be the face of 6393 the new name." 6394 :group 'lsp-mode) 6395 6396 (defvar lsp-rename-history '() 6397 "History for `lsp--read-rename'.") 6398 6399 (defun lsp--read-rename (at-point) 6400 "Read a new name for a `lsp-rename' at `point' from the user. 6401 AT-POINT shall be a structure as returned by 6402 `lsp--get-symbol-to-rename'. 6403 6404 Returns a string, which should be the new name for the identifier 6405 at point. If renaming cannot be done at point (as determined from 6406 AT-POINT), throw a `user-error'. 6407 6408 This function is for use in `lsp-rename' only, and shall not be 6409 relied upon." 6410 (unless at-point 6411 (user-error "`lsp-rename' is invalid here")) 6412 (-let* ((((start . end) . placeholder?) at-point) 6413 ;; Do the `buffer-substring' first to not include `lsp-face-rename' 6414 (rename-me (buffer-substring start end)) 6415 (placeholder (or placeholder? rename-me)) 6416 (placeholder (propertize placeholder 'face 'lsp-rename-placeholder-face)) 6417 6418 overlay) 6419 ;; We need unwind protect, as the user might cancel here, causing the 6420 ;; overlay to linger. 6421 (unwind-protect 6422 (progn 6423 (setq overlay (make-overlay start end)) 6424 (overlay-put overlay 'face 'lsp-face-rename) 6425 6426 (read-string (format "Rename %s to: " rename-me) placeholder 6427 'lsp-rename-history)) 6428 (and overlay (delete-overlay overlay))))) 6429 6430 (defun lsp-rename (newname) 6431 "Rename the symbol (and all references to it) under point to NEWNAME." 6432 (interactive (list (lsp--read-rename (lsp--get-symbol-to-rename)))) 6433 (when-let ((edits (lsp-request "textDocument/rename" 6434 `( :textDocument ,(lsp--text-document-identifier) 6435 :position ,(lsp--cur-position) 6436 :newName ,newname)))) 6437 (lsp--apply-workspace-edit edits 'rename))) 6438 6439 (defun lsp--on-rename-file (old-func old-name new-name &optional ok-if-already-exists?) 6440 "Advice around function `rename-file'. 6441 Applies OLD-FUNC with OLD-NAME, NEW-NAME and OK-IF-ALREADY-EXISTS?. 6442 6443 This advice sends workspace/willRenameFiles before renaming file 6444 to check if server wants to apply any workspaceEdits after renamed." 6445 (if (and lsp-apply-edits-after-file-operations 6446 (lsp--send-will-rename-files-p old-name)) 6447 (let ((params (lsp-make-rename-files-params 6448 :files (vector (lsp-make-file-rename 6449 :oldUri (lsp--path-to-uri old-name) 6450 :newUri (lsp--path-to-uri new-name)))))) 6451 (when-let ((edits (lsp-request "workspace/willRenameFiles" params))) 6452 (lsp--apply-workspace-edit edits 'rename-file) 6453 (funcall old-func old-name new-name ok-if-already-exists?) 6454 (when (lsp--send-did-rename-files-p) 6455 (lsp-notify "workspace/didRenameFiles" params)))) 6456 (funcall old-func old-name new-name ok-if-already-exists?))) 6457 6458 (advice-add 'rename-file :around #'lsp--on-rename-file) 6459 6460 (defcustom lsp-xref-force-references nil 6461 "If non-nil threat everything as references(e. g. jump if only one item.)" 6462 :group 'lsp-mode 6463 :type 'boolean) 6464 6465 (defun lsp-show-xrefs (xrefs display-action references?) 6466 (unless (region-active-p) (push-mark nil t)) 6467 (if (boundp 'xref-show-definitions-function) 6468 (with-no-warnings 6469 (xref-push-marker-stack) 6470 (funcall (if (and references? (not lsp-xref-force-references)) 6471 xref-show-xrefs-function 6472 xref-show-definitions-function) 6473 (-const xrefs) 6474 `((window . ,(selected-window)) 6475 (display-action . ,display-action) 6476 ,(if (and references? (not lsp-xref-force-references)) 6477 `(auto-jump . ,xref-auto-jump-to-first-xref) 6478 `(auto-jump . ,xref-auto-jump-to-first-definition))))) 6479 (xref--show-xrefs xrefs display-action))) 6480 6481 (cl-defmethod seq-empty-p ((ht hash-table)) 6482 "Function `seq-empty-p' for hash-table." 6483 (hash-table-empty-p ht)) 6484 6485 (cl-defun lsp-find-locations (method &optional extra &key display-action references?) 6486 "Send request named METHOD and get cross references of the symbol under point. 6487 EXTRA is a plist of extra parameters. 6488 REFERENCES? t when METHOD returns references." 6489 (let ((loc (lsp-request method 6490 (append (lsp--text-document-position-params) extra)))) 6491 (if (seq-empty-p loc) 6492 (lsp--error "Not found for: %s" (or (thing-at-point 'symbol t) "")) 6493 (lsp-show-xrefs (lsp--locations-to-xref-items loc) display-action references?)))) 6494 6495 (cl-defun lsp-find-declaration (&key display-action) 6496 "Find declarations of the symbol under point." 6497 (interactive) 6498 (lsp-find-locations "textDocument/declaration" nil :display-action display-action)) 6499 6500 (cl-defun lsp-find-definition (&key display-action) 6501 "Find definitions of the symbol under point." 6502 (interactive) 6503 (lsp-find-locations "textDocument/definition" nil :display-action display-action)) 6504 6505 (defun lsp-find-definition-mouse (click) 6506 "Click to start `lsp-find-definition' at clicked point." 6507 (interactive "e") 6508 (let* ((ec (event-start click)) 6509 (p1 (posn-point ec)) 6510 (w1 (posn-window ec))) 6511 (select-window w1) 6512 (goto-char p1) 6513 (lsp-find-definition))) 6514 6515 (cl-defun lsp-find-implementation (&key display-action) 6516 "Find implementations of the symbol under point." 6517 (interactive) 6518 (lsp-find-locations "textDocument/implementation" 6519 nil 6520 :display-action display-action 6521 :references? t)) 6522 6523 (cl-defun lsp-find-references (&optional exclude-declaration &key display-action) 6524 "Find references of the symbol under point." 6525 (interactive "P") 6526 (lsp-find-locations "textDocument/references" 6527 (list :context `(:includeDeclaration ,(lsp-json-bool (not (or exclude-declaration lsp-references-exclude-definition))))) 6528 :display-action display-action 6529 :references? t)) 6530 6531 (cl-defun lsp-find-type-definition (&key display-action) 6532 "Find type definitions of the symbol under point." 6533 (interactive) 6534 (lsp-find-locations "textDocument/typeDefinition" nil :display-action display-action)) 6535 6536 (defalias 'lsp-find-custom #'lsp-find-locations) 6537 (defalias 'lsp-goto-implementation #'lsp-find-implementation) 6538 (defalias 'lsp-goto-type-definition #'lsp-find-type-definition) 6539 6540 (with-eval-after-load 'evil 6541 (evil-set-command-property 'lsp-find-definition :jump t) 6542 (evil-set-command-property 'lsp-find-implementation :jump t) 6543 (evil-set-command-property 'lsp-find-references :jump t) 6544 (evil-set-command-property 'lsp-find-type-definition :jump t)) 6545 6546 (defun lsp--workspace-method-supported? (check-command method capability workspace) 6547 (with-lsp-workspace workspace 6548 (if check-command 6549 (funcall check-command workspace) 6550 (or 6551 (when capability (lsp--capability capability)) 6552 (lsp--registered-capability method) 6553 (and (not capability) (not check-command)))))) 6554 6555 (defun lsp-disable-method-for-server (method server-id) 6556 "Disable METHOD for SERVER-ID." 6557 (cl-callf 6558 (lambda (reqs) 6559 (-let (((&plist :check-command :capability) reqs)) 6560 (list :check-command 6561 (lambda (workspace) 6562 (unless (-> workspace 6563 lsp--workspace-client 6564 lsp--client-server-id 6565 (eq server-id)) 6566 (lsp--workspace-method-supported? check-command 6567 method 6568 capability 6569 workspace)))))) 6570 (alist-get method lsp-method-requirements nil nil 'string=))) 6571 6572 (defun lsp--find-workspaces-for (msg-or-method) 6573 "Find all workspaces in the current project that can handle MSG." 6574 (let ((method (if (stringp msg-or-method) 6575 msg-or-method 6576 (plist-get msg-or-method :method)))) 6577 (-if-let (reqs (cdr (assoc method lsp-method-requirements))) 6578 (-let (((&plist :capability :check-command) reqs)) 6579 (-filter 6580 (-partial #'lsp--workspace-method-supported? 6581 check-command method capability) 6582 (lsp-workspaces))) 6583 (lsp-workspaces)))) 6584 6585 (defun lsp-can-execute-command? (command-name) 6586 "Returns non-nil if current language server(s) can execute COMMAND-NAME. 6587 The command is executed via `workspace/executeCommand'" 6588 (cl-position 6589 command-name 6590 (lsp:execute-command-options-commands 6591 (lsp:server-capabilities-execute-command-provider? 6592 (lsp--server-capabilities))) 6593 :test #'equal)) 6594 6595 (defalias 'lsp-feature? 'lsp--find-workspaces-for) 6596 6597 (cl-defmethod lsp-execute-command (_server _command _arguments) 6598 "Dispatch COMMAND execution." 6599 (signal 'cl-no-applicable-method nil)) 6600 6601 (defun lsp-workspace-command-execute (command &optional args) 6602 "Execute workspace COMMAND with ARGS." 6603 (condition-case-unless-debug err 6604 (let ((params (if args 6605 (list :command command :arguments args) 6606 (list :command command)))) 6607 (lsp-request "workspace/executeCommand" params)) 6608 (error 6609 (error "`workspace/executeCommand' with `%s' failed.\n\n%S" 6610 command err)))) 6611 6612 (defun lsp-send-execute-command (command &optional args) 6613 "Create and send a `workspace/executeCommand' message having command COMMAND 6614 and optional ARGS." 6615 (lsp-workspace-command-execute command args)) 6616 6617 (defalias 'lsp-point-to-position #'lsp--point-to-position) 6618 (defalias 'lsp-text-document-identifier #'lsp--text-document-identifier) 6619 (defalias 'lsp--send-execute-command #'lsp-send-execute-command) 6620 (defalias 'lsp-on-open #'lsp--text-document-did-open) 6621 (defalias 'lsp-on-save #'lsp--text-document-did-save) 6622 6623 (defun lsp--set-configuration (settings) 6624 "Set the SETTINGS for the lsp server." 6625 (lsp-notify "workspace/didChangeConfiguration" `(:settings ,settings))) 6626 6627 (defun lsp-current-buffer () 6628 (or lsp--virtual-buffer 6629 (current-buffer))) 6630 6631 (defun lsp-buffer-live-p (buffer-id) 6632 (if-let ((buffer-live (plist-get buffer-id :buffer-live?))) 6633 (funcall buffer-live buffer-id) 6634 (buffer-live-p buffer-id))) 6635 6636 (defun lsp--on-set-visited-file-name (old-func &rest args) 6637 "Advice around function `set-visited-file-name'. 6638 6639 This advice sends textDocument/didClose for the old file and 6640 textDocument/didOpen for the new file." 6641 (when lsp--cur-workspace 6642 (lsp--text-document-did-close t)) 6643 (prog1 (apply old-func args) 6644 (when lsp--cur-workspace 6645 (lsp--text-document-did-open)))) 6646 6647 (advice-add 'set-visited-file-name :around #'lsp--on-set-visited-file-name) 6648 6649 (defvar lsp--flushing-delayed-changes nil) 6650 6651 (defun lsp--send-no-wait (message proc) 6652 "Send MESSAGE to PROC without waiting for further output." 6653 6654 (unless lsp--flushing-delayed-changes 6655 (let ((lsp--flushing-delayed-changes t)) 6656 (lsp--flush-delayed-changes))) 6657 (lsp-process-send proc message)) 6658 6659 (define-error 'lsp-parse-error 6660 "Error parsing message from language server" 'lsp-error) 6661 (define-error 'lsp-unknown-message-type 6662 "Unknown message type" '(lsp-error lsp-parse-error)) 6663 (define-error 'lsp-unknown-json-rpc-version 6664 "Unknown JSON-RPC protocol version" '(lsp-error lsp-parse-error)) 6665 (define-error 'lsp-no-content-length 6666 "Content-Length header missing in message" '(lsp-error lsp-parse-error)) 6667 (define-error 'lsp-invalid-header-name 6668 "Invalid header name" '(lsp-error lsp-parse-error)) 6669 6670 ;; id method 6671 ;; x x request 6672 ;; x . response 6673 ;; . x notification 6674 (defun lsp--get-message-type (json-data) 6675 "Get the message type from JSON-DATA." 6676 (if (lsp:json-message-id? json-data) 6677 (if (lsp:json-message-error? json-data) 6678 'response-error 6679 (if (lsp:json-message-method? json-data) 6680 'request 6681 'response)) 6682 'notification)) 6683 6684 (defconst lsp--default-notification-handlers 6685 (ht ("window/showMessage" #'lsp--window-show-message) 6686 ("window/logMessage" #'lsp--window-log-message) 6687 ("window/showInputBox" #'lsp--window-show-input-box) 6688 ("window/showQuickPick" #'lsp--window-show-quick-pick) 6689 ("textDocument/publishDiagnostics" #'lsp--on-diagnostics) 6690 ("textDocument/diagnosticsEnd" #'ignore) 6691 ("textDocument/diagnosticsBegin" #'ignore) 6692 ("telemetry/event" #'ignore) 6693 ("$/progress" (lambda (workspace params) 6694 (funcall lsp-progress-function workspace params))))) 6695 6696 (lsp-defun lsp--on-notification (workspace (&JSONNotification :params :method)) 6697 "Call the appropriate handler for NOTIFICATION." 6698 (-let ((client (lsp--workspace-client workspace))) 6699 (when (lsp--log-io-p method) 6700 (lsp--log-entry-new (lsp--make-log-entry method nil params 'incoming-notif) 6701 lsp--cur-workspace)) 6702 (if-let ((handler (or (gethash method (lsp--client-notification-handlers client)) 6703 (gethash method lsp--default-notification-handlers)))) 6704 (funcall handler workspace params) 6705 (when (and method (not (string-prefix-p "$" method))) 6706 (lsp-warn "Unknown notification: %s" method))))) 6707 6708 (lsp-defun lsp--build-workspace-configuration-response ((&ConfigurationParams :items)) 6709 "Get section configuration. 6710 PARAMS are the `workspace/configuration' request params" 6711 (->> items 6712 (-map (-lambda ((&ConfigurationItem :section?)) 6713 (-let* ((path-parts (split-string section? "\\.")) 6714 (path-without-last (s-join "." (-slice path-parts 0 -1))) 6715 (path-parts-len (length path-parts))) 6716 (cond 6717 ((<= path-parts-len 1) 6718 (ht-get (lsp-configuration-section section?) 6719 (car-safe path-parts) 6720 (ht-create))) 6721 ((> path-parts-len 1) 6722 (when-let ((section (lsp-configuration-section path-without-last)) 6723 (keys path-parts)) 6724 (while (and keys section) 6725 (setf section (ht-get section (pop keys)))) 6726 section)))))) 6727 (apply #'vector))) 6728 6729 (defun lsp--ms-since (timestamp) 6730 "Integer number of milliseconds since TIMESTAMP. Fractions discarded." 6731 (floor (* 1000 (float-time (time-since timestamp))))) 6732 6733 (defun lsp--send-request-response (workspace recv-time request response) 6734 "Send the RESPONSE for REQUEST in WORKSPACE and log if needed." 6735 (-let* (((&JSONResponse :params :method :id) request) 6736 (process (lsp--workspace-proc workspace)) 6737 (response (lsp--make-response id response)) 6738 (req-entry (and lsp-log-io 6739 (lsp--make-log-entry method id params 'incoming-req))) 6740 (resp-entry (and lsp-log-io 6741 (lsp--make-log-entry method id response 'outgoing-resp 6742 (lsp--ms-since recv-time))))) 6743 ;; Send response to the server. 6744 (when (lsp--log-io-p method) 6745 (lsp--log-entry-new req-entry workspace) 6746 (lsp--log-entry-new resp-entry workspace)) 6747 (lsp--send-no-wait response process))) 6748 6749 (lsp-defun lsp--on-request (workspace (request &as &JSONRequest :params :method)) 6750 "Call the appropriate handler for REQUEST, and send the return value to the 6751 server. WORKSPACE is the active workspace." 6752 (-let* ((recv-time (current-time)) 6753 (client (lsp--workspace-client workspace)) 6754 (buffers (lsp--workspace-buffers workspace)) 6755 handler 6756 (response (cond 6757 ((setq handler (gethash method (lsp--client-request-handlers client) nil)) 6758 (funcall handler workspace params)) 6759 ((setq handler (gethash method (lsp--client-async-request-handlers client) nil)) 6760 (funcall handler workspace params 6761 (-partial #'lsp--send-request-response 6762 workspace recv-time request)) 6763 'delay-response) 6764 ((equal method "client/registerCapability") 6765 (mapc #'lsp--server-register-capability 6766 (lsp:registration-params-registrations params)) 6767 (mapc (lambda (buf) 6768 (when (lsp-buffer-live-p buf) 6769 (lsp-with-current-buffer buf 6770 (lsp-unconfig-buffer) 6771 (lsp-configure-buffer)))) 6772 buffers) 6773 nil) 6774 ((equal method "window/showMessageRequest") 6775 (let ((choice (lsp--window-log-message-request params))) 6776 `(:title ,choice))) 6777 ((equal method "window/showDocument") 6778 (let ((success? (lsp--window-show-document params))) 6779 (lsp-make-show-document-result :success (or success? 6780 :json-false)))) 6781 ((equal method "client/unregisterCapability") 6782 (mapc #'lsp--server-unregister-capability 6783 (lsp:unregistration-params-unregisterations params)) 6784 (mapc (lambda (buf) 6785 (when (lsp-buffer-live-p buf) 6786 (lsp-with-current-buffer buf 6787 (lsp-unconfig-buffer) 6788 (lsp-configure-buffer)))) 6789 buffers) 6790 nil) 6791 ((equal method "workspace/applyEdit") 6792 (list :applied (condition-case err 6793 (prog1 t 6794 (lsp--apply-workspace-edit (lsp:apply-workspace-edit-params-edit params) 'server-requested)) 6795 (error 6796 (lsp--error "Failed to apply edits with message %s" 6797 (error-message-string err)) 6798 :json-false)))) 6799 ((equal method "workspace/configuration") 6800 (with-lsp-workspace workspace 6801 (if-let ((buf (car buffers))) 6802 (lsp-with-current-buffer buf 6803 (lsp--build-workspace-configuration-response params)) 6804 (lsp--with-workspace-temp-buffer (lsp--workspace-root workspace) 6805 (lsp--build-workspace-configuration-response params))))) 6806 ((equal method "workspace/workspaceFolders") 6807 (let ((folders (or (-> workspace 6808 (lsp--workspace-client) 6809 (lsp--client-server-id) 6810 (gethash (lsp-session-server-id->folders (lsp-session)))) 6811 (lsp-session-folders (lsp-session))))) 6812 (->> folders 6813 (-distinct) 6814 (-map (lambda (folder) 6815 (list :uri (lsp--path-to-uri folder)))) 6816 (apply #'vector)))) 6817 ((equal method "window/workDoneProgress/create") 6818 nil ;; no specific reply, no processing required 6819 ) 6820 ((equal method "workspace/semanticTokens/refresh") 6821 (when (and lsp-semantic-tokens-enable 6822 (fboundp 'lsp--semantic-tokens-on-refresh)) 6823 (lsp--semantic-tokens-on-refresh workspace)) 6824 nil) 6825 ((equal method "workspace/codeLens/refresh") 6826 (when (and lsp-lens-enable 6827 (fboundp 'lsp--lens-on-refresh)) 6828 (lsp--lens-on-refresh workspace)) 6829 nil) 6830 (t (lsp-warn "Unknown request method: %s" method) nil)))) 6831 ;; Send response to the server. 6832 (unless (eq response 'delay-response) 6833 (lsp--send-request-response workspace recv-time request response)))) 6834 6835 (lsp-defun lsp--error-string ((&JSONError :message :code)) 6836 "Format ERR as a user friendly string." 6837 (format "Error from the Language Server: %s (%s)" 6838 message 6839 (or (car (alist-get code lsp--errors)) "Unknown error"))) 6840 6841 (defun lsp--get-body-length (headers) 6842 (let ((content-length (cdr (assoc "Content-Length" headers)))) 6843 (if content-length 6844 (string-to-number content-length) 6845 6846 ;; This usually means either the server or our parser is 6847 ;; screwed up with a previous Content-Length 6848 (error "No Content-Length header")))) 6849 6850 (defun lsp--parse-header (s) 6851 "Parse string S as a LSP (KEY . VAL) header." 6852 (let ((pos (string-match "\:" s)) 6853 key val) 6854 (unless pos 6855 (signal 'lsp-invalid-header-name (list s))) 6856 (setq key (substring s 0 pos) 6857 val (s-trim-left (substring s (+ 1 pos)))) 6858 (when (equal key "Content-Length") 6859 (cl-assert (cl-loop for c across val 6860 when (or (> c ?9) (< c ?0)) return nil 6861 finally return t) 6862 nil (format "Invalid Content-Length value: %s" val))) 6863 (cons key val))) 6864 6865 (defmacro lsp--read-json (str) 6866 "Read json string STR." 6867 (if (progn 6868 (require 'json) 6869 (fboundp 'json-parse-string)) 6870 `(json-parse-string ,str 6871 :object-type (if lsp-use-plists 6872 'plist 6873 'hash-table) 6874 :null-object nil 6875 :false-object nil) 6876 `(let ((json-array-type 'vector) 6877 (json-object-type (if lsp-use-plists 6878 'plist 6879 'hash-table)) 6880 (json-false nil)) 6881 (json-read-from-string ,str)))) 6882 6883 (defmacro lsp-json-read-buffer () 6884 "Read json from the current buffer." 6885 (if (progn 6886 (require 'json) 6887 (fboundp 'json-parse-buffer)) 6888 `(json-parse-buffer :object-type (if lsp-use-plists 6889 'plist 6890 'hash-table) 6891 :null-object nil 6892 :false-object nil) 6893 `(let ((json-array-type 'vector) 6894 (json-object-type (if lsp-use-plists 6895 'plist 6896 'hash-table)) 6897 (json-false nil)) 6898 (json-read)))) 6899 6900 (defun lsp--read-json-file (file-path) 6901 "Read json file." 6902 (-> file-path 6903 (f-read-text) 6904 (lsp--read-json))) 6905 6906 (defun lsp--parser-on-message (json-data workspace) 6907 "Called when the parser P read a complete MSG from the server." 6908 (with-demoted-errors "Error processing message %S." 6909 (with-lsp-workspace workspace 6910 (let* ((client (lsp--workspace-client workspace)) 6911 (id (--when-let (lsp:json-response-id json-data) 6912 (if (stringp it) (string-to-number it) it))) 6913 (data (lsp:json-response-result json-data))) 6914 (pcase (lsp--get-message-type json-data) 6915 ('response 6916 (cl-assert id) 6917 (-let [(callback _ method _ before-send) (gethash id (lsp--client-response-handlers client))] 6918 (when (lsp--log-io-p method) 6919 (lsp--log-entry-new 6920 (lsp--make-log-entry method id data 'incoming-resp 6921 (lsp--ms-since before-send)) 6922 workspace)) 6923 (when callback 6924 (remhash id (lsp--client-response-handlers client)) 6925 (funcall callback (lsp:json-response-result json-data))))) 6926 ('response-error 6927 (cl-assert id) 6928 (-let [(_ callback method _ before-send) (gethash id (lsp--client-response-handlers client))] 6929 (when (lsp--log-io-p method) 6930 (lsp--log-entry-new 6931 (lsp--make-log-entry method id (lsp:json-response-error-error json-data) 6932 'incoming-resp (lsp--ms-since before-send)) 6933 workspace)) 6934 (when callback 6935 (remhash id (lsp--client-response-handlers client)) 6936 (funcall callback (lsp:json-response-error-error json-data))))) 6937 ('notification 6938 (lsp--on-notification workspace json-data)) 6939 ('request (lsp--on-request workspace json-data))))))) 6940 6941 (defun lsp--create-filter-function (workspace) 6942 "Make filter for the workspace." 6943 (let ((body-received 0) 6944 leftovers body-length body chunk) 6945 (lambda (_proc input) 6946 (setf chunk (if (s-blank? leftovers) 6947 input 6948 (concat leftovers input))) 6949 6950 (let (messages) 6951 (while (not (s-blank? chunk)) 6952 (if (not body-length) 6953 ;; Read headers 6954 (if-let ((body-sep-pos (string-match-p "\r\n\r\n" chunk))) 6955 ;; We've got all the headers, handle them all at once: 6956 (setf body-length (lsp--get-body-length 6957 (mapcar #'lsp--parse-header 6958 (split-string 6959 (substring-no-properties chunk 6960 (or (string-match-p "Content-Length" chunk) 6961 (error "Unable to find Content-Length header.")) 6962 body-sep-pos) 6963 "\r\n"))) 6964 body-received 0 6965 leftovers nil 6966 chunk (substring-no-properties chunk (+ body-sep-pos 4))) 6967 6968 ;; Haven't found the end of the headers yet. Save everything 6969 ;; for when the next chunk arrives and await further input. 6970 (setf leftovers chunk 6971 chunk nil)) 6972 (let* ((chunk-length (string-bytes chunk)) 6973 (left-to-receive (- body-length body-received)) 6974 (this-body (if (< left-to-receive chunk-length) 6975 (prog1 (substring-no-properties chunk 0 left-to-receive) 6976 (setf chunk (substring-no-properties chunk left-to-receive))) 6977 (prog1 chunk 6978 (setf chunk nil)))) 6979 (body-bytes (string-bytes this-body))) 6980 (push this-body body) 6981 (setf body-received (+ body-received body-bytes)) 6982 (when (>= chunk-length left-to-receive) 6983 (condition-case err 6984 (with-temp-buffer 6985 (apply #'insert 6986 (nreverse 6987 (prog1 body 6988 (setf leftovers nil 6989 body-length nil 6990 body-received nil 6991 body nil)))) 6992 (decode-coding-region (point-min) 6993 (point-max) 6994 'utf-8) 6995 (goto-char (point-min)) 6996 (push (lsp-json-read-buffer) messages)) 6997 6998 (error 6999 (lsp-warn "Failed to parse the following chunk:\n'''\n%s\n'''\nwith message %s" 7000 (concat leftovers input) 7001 err))))))) 7002 (mapc (lambda (msg) 7003 (lsp--parser-on-message msg workspace)) 7004 (nreverse messages)))))) 7005 7006 (defvar-local lsp--line-col-to-point-hash-table nil 7007 "Hash table with keys (line . col) and values that are either point positions 7008 or markers.") 7009 7010 (defcustom lsp-imenu-detailed-outline t 7011 "Whether `lsp-imenu' should include signatures. 7012 This will be ignored if the server doesn't provide the necessary 7013 information, for example if it doesn't support DocumentSymbols." 7014 :group 'lsp-imenu 7015 :type 'boolean) 7016 7017 (defcustom lsp-imenu-hide-parent-details t 7018 "Whether `lsp-imenu' should hide signatures of parent nodes." 7019 :group 'lsp-imenu 7020 :type 'boolean) 7021 7022 (defface lsp-details-face '((t :height 0.8 :inherit shadow)) 7023 "Used to display additional information throughout `lsp'. 7024 Things like line numbers, signatures, ... are considered 7025 additional information. Often, additional faces are defined that 7026 inherit from this face by default, like `lsp-signature-face', and 7027 they may be customized for finer control." 7028 :group 'lsp-mode) 7029 7030 (defface lsp-signature-face '((t :inherit lsp-details-face)) 7031 "Used to display signatures in `imenu', ...." 7032 :group 'lsp-mode) 7033 7034 (lsp-defun lsp-render-symbol ((&DocumentSymbol :name :detail? :deprecated?) 7035 show-detail?) 7036 "Render INPUT0, an `&DocumentSymbol', to a string. 7037 If SHOW-DETAIL? is set, make use of its `:detail?' field (often 7038 the signature)." 7039 (let ((detail (and show-detail? (s-present? detail?) 7040 (propertize (concat " " (s-trim-left detail?)) 7041 'face 'lsp-signature-face))) 7042 (name (if deprecated? 7043 (propertize name 'face 'lsp-face-semhl-deprecated) name))) 7044 (concat name detail))) 7045 7046 (lsp-defun lsp-render-symbol-information ((&SymbolInformation :name :deprecated? :container-name?) 7047 separator) 7048 "Render a piece of SymbolInformation. 7049 Handle :deprecated?. If SEPARATOR is non-nil, the 7050 symbol's (optional) parent, SEPARATOR and the symbol itself are 7051 concatenated." 7052 (when (and separator container-name? (not (string-empty-p container-name?))) 7053 (setq name (concat name separator container-name?))) 7054 (if deprecated? (propertize name 'face 'lsp-face-semhl-deprecated) name)) 7055 7056 (defun lsp--symbol-to-imenu-elem (sym) 7057 "Convert SYM to imenu element. 7058 7059 SYM is a SymbolInformation message. 7060 7061 Return a cons cell (full-name . start-point)." 7062 (let ((start-point (ht-get lsp--line-col-to-point-hash-table 7063 (lsp--get-line-and-col sym)))) 7064 (cons (lsp-render-symbol-information 7065 sym (and lsp-imenu-show-container-name 7066 lsp-imenu-container-name-separator)) 7067 start-point))) 7068 7069 (lsp-defun lsp--symbol-to-hierarchical-imenu-elem ((sym &as &DocumentSymbol :children?)) 7070 "Convert SYM to hierarchical imenu elements. 7071 7072 SYM is a DocumentSymbol message. 7073 7074 Return cons cell (\"symbol-name (symbol-kind)\" . start-point) if 7075 SYM doesn't have any children. Otherwise return a cons cell with 7076 an alist 7077 7078 (\"symbol-name\" . ((\"(symbol-kind)\" . start-point) 7079 cons-cells-from-children))" 7080 (let ((filtered-children (lsp--imenu-filter-symbols children?)) 7081 (signature (lsp-render-symbol sym lsp-imenu-detailed-outline))) 7082 (if (seq-empty-p filtered-children) 7083 (cons signature 7084 (ht-get lsp--line-col-to-point-hash-table 7085 (lsp--get-line-and-col sym))) 7086 (cons signature 7087 (lsp--imenu-create-hierarchical-index filtered-children))))) 7088 7089 (lsp-defun lsp--symbol-ignore ((&SymbolInformation :kind)) 7090 "Determine if SYM is for the current document and is to be shown." 7091 ;; It's a SymbolInformation or DocumentSymbol, which is always in the 7092 ;; current buffer file. 7093 (and lsp-imenu-index-symbol-kinds 7094 (numberp kind) 7095 (let ((clamped-kind (if (< 0 kind (length lsp/symbol-kind-lookup)) 7096 kind 7097 0))) 7098 (not (memql (aref lsp/symbol-kind-lookup clamped-kind) 7099 lsp-imenu-index-symbol-kinds))))) 7100 7101 (lsp-defun lsp--get-symbol-type ((&SymbolInformation :kind)) 7102 "The string name of the kind of SYM." 7103 (alist-get kind lsp-symbol-kinds "Other")) 7104 7105 (defun lsp--get-line-and-col (sym) 7106 "Obtain the line and column corresponding to SYM." 7107 (-let* ((location (lsp:symbol-information-location sym)) 7108 (name-range (or (and location (lsp:location-range location)) 7109 (lsp:document-symbol-selection-range sym))) 7110 ((&Range :start (&Position :line :character)) name-range)) 7111 (cons line character))) 7112 7113 (defun lsp--collect-lines-and-cols (symbols) 7114 "Return a sorted list ((line . col) ...) of the locations of SYMBOLS." 7115 (let ((stack (mapcar 'identity symbols)) 7116 line-col-list) 7117 (while stack 7118 (let ((sym (pop stack))) 7119 (push (lsp--get-line-and-col sym) line-col-list) 7120 (unless (seq-empty-p (lsp:document-symbol-children? sym)) 7121 (setf stack (nconc (lsp--imenu-filter-symbols (lsp:document-symbol-children? sym)) stack))))) 7122 (-sort #'lsp--line-col-comparator line-col-list))) 7123 7124 (defun lsp--convert-line-col-to-points-batch (line-col-list) 7125 "Convert a sorted list of positions from line-column 7126 representation to point representation." 7127 (let ((line-col-to-point-map (ht-create)) 7128 (inhibit-field-text-motion t) 7129 (curr-line 0)) 7130 (lsp-save-restriction-and-excursion 7131 (goto-char (point-min)) 7132 (cl-loop for (line . col) in line-col-list do 7133 (forward-line (- line curr-line)) 7134 (setq curr-line line) 7135 (let ((line-end (line-end-position))) 7136 (if (or (not col) (> col (- line-end (point)))) 7137 (goto-char line-end) 7138 (forward-char col))) 7139 (ht-set! line-col-to-point-map (cons line col) (if imenu-use-markers 7140 (point-marker) 7141 (point))))) 7142 line-col-to-point-map)) 7143 7144 (cl-defun lsp--line-col-comparator ((l1 . c1) (l2 . c2)) 7145 (or (< l1 l2) 7146 (and (= l1 l2) 7147 (cond ((and c1 c2) 7148 (< c1 c2)) 7149 (c1 t))))) 7150 7151 (defun lsp-imenu-create-uncategorized-index (symbols) 7152 "Create imenu index from document SYMBOLS. 7153 This function, unlike `lsp-imenu-create-categorized-index', does 7154 not categorize by type, but instead returns an `imenu' index 7155 corresponding to the symbol hierarchy returned by the server 7156 directly." 7157 (let* ((lsp--line-col-to-point-hash-table (-> symbols 7158 lsp--collect-lines-and-cols 7159 lsp--convert-line-col-to-points-batch))) 7160 (if (lsp--imenu-hierarchical-p symbols) 7161 (lsp--imenu-create-hierarchical-index symbols) 7162 (lsp--imenu-create-non-hierarchical-index symbols)))) 7163 7164 (defcustom lsp-imenu-symbol-kinds 7165 '((1 . "Files") 7166 (2 . "Modules") 7167 (3 . "Namespaces") 7168 (4 . "Packages") 7169 (5 . "Classes") 7170 (6 . "Methods") 7171 (7 . "Properties") 7172 (8 . "Fields") 7173 (9 . "Constructors") 7174 (10 . "Enums") 7175 (11 . "Interfaces") 7176 (12 . "Functions") 7177 (13 . "Variables") 7178 (14 . "Constants") 7179 (15 . "Strings") 7180 (16 . "Numbers") 7181 (17 . "Booleans") 7182 (18 . "Arrays") 7183 (19 . "Objects") 7184 (20 . "Keys") 7185 (21 . "Nulls") 7186 (22 . "Enum Members") 7187 (23 . "Structs") 7188 (24 . "Events") 7189 (25 . "Operators") 7190 (26 . "Type Parameters")) 7191 "`lsp-symbol-kinds', but only used by `imenu'. 7192 A new variable is needed, as it is `imenu' convention to use 7193 pluralized categories, which `lsp-symbol-kinds' doesn't. If the 7194 non-pluralized names are preferred, this can be set to 7195 `lsp-symbol-kinds'." 7196 :type '(alist :key-type integer :value-type string)) 7197 7198 (defun lsp--imenu-kind->name (kind) 7199 (alist-get kind lsp-imenu-symbol-kinds "?")) 7200 7201 (defun lsp-imenu-create-top-level-categorized-index (symbols) 7202 "Create an `imenu' index categorizing SYMBOLS by type. 7203 Only root symbols are categorized. 7204 7205 See `lsp-symbol-kinds' to customize the category naming. SYMBOLS 7206 shall be a list of DocumentSymbols or SymbolInformation." 7207 (mapcan 7208 (-lambda ((type . symbols)) 7209 (let ((cat (lsp--imenu-kind->name type)) 7210 (symbols (lsp-imenu-create-uncategorized-index symbols))) 7211 ;; If there is no :kind (this is being defensive), or we couldn't look it 7212 ;; up, just display the symbols inline, without categories. 7213 (if cat (list (cons cat symbols)) symbols))) 7214 (sort (seq-group-by #'lsp:document-symbol-kind symbols) 7215 (-lambda ((kinda) (kindb)) (< kinda kindb))))) 7216 7217 (lsp-defun lsp--symbol->imenu ((sym &as &DocumentSymbol :selection-range (&RangeToPoint :start))) 7218 "Convert an `&DocumentSymbol' to an `imenu' entry." 7219 (cons (lsp-render-symbol sym lsp-imenu-detailed-outline) start)) 7220 7221 (defun lsp--imenu-create-categorized-index-1 (symbols) 7222 "Returns an `imenu' index from SYMBOLS categorized by type. 7223 The result looks like this: ((\"Variables\" . (...)))." 7224 (->> 7225 symbols 7226 (mapcan 7227 (-lambda ((sym &as &DocumentSymbol :kind :children?)) 7228 (if (seq-empty-p children?) 7229 (list (list kind (lsp--symbol->imenu sym))) 7230 (let ((parent (lsp-render-symbol sym (and lsp-imenu-detailed-outline 7231 (not lsp-imenu-hide-parent-details))))) 7232 (cons 7233 (list kind (lsp--symbol->imenu sym)) 7234 (mapcar (-lambda ((type . imenu-items)) 7235 (list type (cons parent (mapcan #'cdr imenu-items)))) 7236 (-group-by #'car (lsp--imenu-create-categorized-index-1 children?)))))))) 7237 (-group-by #'car) 7238 (mapcar 7239 (-lambda ((kind . syms)) 7240 (cons kind (mapcan #'cdr syms)))))) 7241 7242 (defun lsp--imenu-create-categorized-index (symbols) 7243 (let ((syms (lsp--imenu-create-categorized-index-1 symbols))) 7244 (dolist (sym syms) 7245 (setcar sym (lsp--imenu-kind->name (car sym)))) 7246 syms)) 7247 7248 (lsp-defun lsp--symbol-information->imenu ((sym &as &SymbolInformation :location (&Location :range (&RangeToPoint :start)))) 7249 (cons (lsp-render-symbol-information sym nil) start)) 7250 7251 (defun lsp--imenu-create-categorized-index-flat (symbols) 7252 "Create a kind-categorized index for SymbolInformation." 7253 (mapcar (-lambda ((kind . syms)) 7254 (cons (lsp--imenu-kind->name kind) 7255 (mapcan (-lambda ((parent . children)) 7256 (let ((children (mapcar #'lsp--symbol-information->imenu children))) 7257 (if parent (list (cons parent children)) children))) 7258 (-group-by #'lsp:symbol-information-container-name? syms)))) 7259 (seq-group-by #'lsp:symbol-information-kind symbols))) 7260 7261 (defun lsp-imenu-create-categorized-index (symbols) 7262 (if (lsp--imenu-hierarchical-p symbols) 7263 (lsp--imenu-create-categorized-index symbols) 7264 (lsp--imenu-create-categorized-index-flat symbols))) 7265 7266 (defcustom lsp-imenu-index-function #'lsp-imenu-create-uncategorized-index 7267 "Function that should create an `imenu' index. 7268 It will be called with a list of SymbolInformation or 7269 DocumentSymbols, whose first level is already filtered. It shall 7270 then return an appropriate `imenu' index (see 7271 `imenu-create-index-function'). 7272 7273 Note that this interface is not stable, and subject to change any 7274 time." 7275 :group 'lsp-imenu 7276 :type '(radio 7277 (const :tag "Categorize by type" 7278 lsp-imenu-create-categorized-index) 7279 (const :tag "Categorize root symbols by type" 7280 lsp-imenu-create-top-level-categorized-index) 7281 (const :tag "Uncategorized, inline entries" 7282 lsp-imenu-create-uncategorized-index) 7283 (function :tag "Custom function"))) 7284 7285 (defun lsp--imenu-create-index () 7286 "Create an `imenu' index based on the language server. 7287 Respects `lsp-imenu-index-function'." 7288 (let ((symbols (lsp--imenu-filter-symbols (lsp--get-document-symbols)))) 7289 (funcall lsp-imenu-index-function symbols))) 7290 7291 (defun lsp--imenu-filter-symbols (symbols) 7292 "Filter out unsupported symbols from SYMBOLS." 7293 (seq-remove #'lsp--symbol-ignore symbols)) 7294 7295 (defun lsp--imenu-hierarchical-p (symbols) 7296 "Determine whether any element in SYMBOLS has children." 7297 (seq-some #'lsp-document-symbol? symbols)) 7298 7299 (defun lsp--imenu-create-non-hierarchical-index (symbols) 7300 "Create imenu index for non-hierarchical SYMBOLS. 7301 7302 SYMBOLS are a list of DocumentSymbol messages. 7303 7304 Return a nested alist keyed by symbol names. e.g. 7305 7306 ((\"SomeClass\" (\"(Class)\" . 10) 7307 (\"someField (Field)\" . 20) 7308 (\"someFunction (Function)\" . 25) 7309 (\"SomeSubClass\" (\"(Class)\" . 30) 7310 (\"someSubField (Field)\" . 35)) 7311 (\"someFunction (Function)\" . 40))" 7312 (seq-map (lambda (nested-alist) 7313 (cons (car nested-alist) 7314 (seq-map #'lsp--symbol-to-imenu-elem (cdr nested-alist)))) 7315 (seq-group-by #'lsp--get-symbol-type symbols))) 7316 7317 (defun lsp--imenu-create-hierarchical-index (symbols) 7318 "Create imenu index for hierarchical SYMBOLS. 7319 7320 SYMBOLS are a list of DocumentSymbol messages. 7321 7322 Return a nested alist keyed by symbol names. e.g. 7323 7324 ((\"SomeClass\" (\"(Class)\" . 10) 7325 (\"someField (Field)\" . 20) 7326 (\"someFunction (Function)\" . 25) 7327 (\"SomeSubClass\" (\"(Class)\" . 30) 7328 (\"someSubField (Field)\" . 35)) 7329 (\"someFunction (Function)\" . 40))" 7330 (seq-map #'lsp--symbol-to-hierarchical-imenu-elem 7331 (seq-sort #'lsp--imenu-symbol-lessp symbols))) 7332 7333 (defun lsp--imenu-symbol-lessp (sym1 sym2) 7334 (let* ((compare-results (mapcar (lambda (method) 7335 (funcall (alist-get method lsp--imenu-compare-function-alist) 7336 sym1 sym2)) 7337 lsp-imenu-sort-methods)) 7338 (result (seq-find (lambda (result) 7339 (not (= result 0))) 7340 compare-results 7341 0))) 7342 (and (numberp result) (< result 0)))) 7343 7344 (lsp-defun lsp--imenu-compare-kind ((&SymbolInformation :kind left) 7345 (&SymbolInformation :kind right)) 7346 "Compare SYM1 and SYM2 by kind." 7347 (- left right)) 7348 7349 (defun lsp--imenu-compare-line-col (sym1 sym2) 7350 (if (lsp--line-col-comparator 7351 (lsp--get-line-and-col sym1) 7352 (lsp--get-line-and-col sym2)) 7353 -1 7354 1)) 7355 7356 (lsp-defun lsp--imenu-compare-name ((&SymbolInformation :name name1) 7357 (&SymbolInformation :name name2)) 7358 "Compare SYM1 and SYM2 by name." 7359 (let ((result (compare-strings name1 0 (length name1) name2 0 (length name2)))) 7360 (if (numberp result) result 0))) 7361 7362 (defun lsp--imenu-refresh () 7363 "Force Imenu to refresh itself." 7364 (imenu--menubar-select imenu--rescan-item)) 7365 7366 (defun lsp-enable-imenu () 7367 "Use lsp-imenu for the current buffer." 7368 (imenu--cleanup) 7369 (add-function :override (local 'imenu-create-index-function) #'lsp--imenu-create-index) 7370 (setq-local imenu-menubar-modified-tick -1) 7371 (setq-local imenu--index-alist nil) 7372 (when menu-bar-mode 7373 (lsp--imenu-refresh))) 7374 7375 (defun lsp-resolve-final-command (command &optional test?) 7376 "Resolve final function COMMAND." 7377 (let* ((command (lsp-resolve-value command)) 7378 (command (cl-etypecase command 7379 (list 7380 (cl-assert (seq-every-p (apply-partially #'stringp) command) nil 7381 "Invalid command list") 7382 command) 7383 (string (list command))))) 7384 (if (and (file-remote-p default-directory) (not test?)) 7385 (list shell-file-name "-c" 7386 (string-join (cons "stty raw > /dev/null;" 7387 (mapcar #'shell-quote-argument command)) 7388 " ")) 7389 command))) 7390 7391 (defun lsp-server-present? (final-command) 7392 "Check whether FINAL-COMMAND is present." 7393 (let ((binary-found? (executable-find (cl-first final-command) t))) 7394 (if binary-found? 7395 (lsp-log "Command \"%s\" is present on the path." (s-join " " final-command)) 7396 (lsp-log "Command \"%s\" is not present on the path." (s-join " " final-command))) 7397 binary-found?)) 7398 7399 (defun lsp--value-to-string (value) 7400 "Convert VALUE to a string that can be set as value in an environment 7401 variable." 7402 (cond 7403 ((stringp value) value) 7404 ((booleanp value) (if value 7405 "1" 7406 "0")) 7407 ((and (sequencep value) 7408 (seq-every-p #'stringp value)) (string-join value ":")) 7409 (t (user-error "Only strings, booleans, and sequences of strings are supported as environment variables")))) 7410 7411 (defun lsp--compute-process-environment (environment-fn) 7412 "Append a list of KEY=VALUE from the alist ENVIRONMENT to `process-environment'. 7413 Ignore non-boolean keys whose value is nil." 7414 (let ((environment (if environment-fn 7415 (funcall environment-fn) 7416 nil))) 7417 (-flatten (cons (cl-loop for (key . value) in environment 7418 if (or (eval value) 7419 (eq (get value 'custom-type) 'boolean)) 7420 collect (concat key "=" (lsp--value-to-string 7421 (eval value)))) 7422 process-environment)))) 7423 7424 (defun lsp--default-directory-for-connection (&optional path) 7425 "Return path to be used for the working directory of a LSP process. 7426 7427 If `lsp-use-workspace-root-for-server-default-directory' is 7428 non-nil, uses `lsp-workspace-root' to find the directory 7429 corresponding to PATH, else returns `default-directory'." 7430 (if lsp-use-workspace-root-for-server-default-directory 7431 (lsp-workspace-root path) 7432 default-directory)) 7433 7434 (defun lsp--fix-remote-cmd (program) 7435 "Helper for `lsp-stdio-connection'. 7436 Originally coppied from eglot." 7437 7438 (if (file-remote-p default-directory) 7439 (list shell-file-name "-c" 7440 (string-join (cons "stty raw > /dev/null;" 7441 (mapcar #'shell-quote-argument program)) 7442 " ")) 7443 program)) 7444 7445 (defvar tramp-use-ssh-controlmaster-options) 7446 (defvar tramp-ssh-controlmaster-options) 7447 7448 (defun lsp-stdio-connection (command &optional test-command) 7449 "Returns a connection property list using COMMAND. 7450 COMMAND can be: A string, denoting the command to launch the 7451 language server. A list of strings, denoting an executable with 7452 its command line arguments. A function, that either returns a 7453 string or a list of strings. In all cases, the launched language 7454 server should send and receive messages on standard I/O. 7455 TEST-COMMAND is a function with no arguments which returns 7456 whether the command is present or not. When not specified 7457 `lsp-mode' will check whether the first element of the list 7458 returned by COMMAND is available via `executable-find'" 7459 (cl-check-type command (or string 7460 function 7461 (and list 7462 (satisfies (lambda (l) 7463 (seq-every-p (lambda (el) 7464 (stringp el)) 7465 l)))))) 7466 (list :connect (lambda (filter sentinel name environment-fn workspace) 7467 (if (and (functionp 'json-rpc-connection) 7468 (not (file-remote-p default-directory))) 7469 (lsp-json-rpc-connection workspace (lsp-resolve-final-command command)) 7470 (let ((final-command (lsp-resolve-final-command command)) 7471 (process-name (generate-new-buffer-name name)) 7472 (process-environment 7473 (lsp--compute-process-environment environment-fn))) 7474 (let* ((stderr-buf (get-buffer-create (format "*%s::stderr*" process-name))) 7475 (default-directory (lsp--default-directory-for-connection)) 7476 (tramp-use-ssh-controlmaster-options 'suppress) 7477 (tramp-ssh-controlmaster-options "-o ControlMaster=no -o ControlPath=none") 7478 (proc (make-process 7479 :name process-name 7480 :connection-type 'pipe 7481 :buffer (format "*%s*" process-name) 7482 :coding 'no-conversion 7483 :command final-command 7484 :filter filter 7485 :sentinel sentinel 7486 :stderr stderr-buf 7487 :noquery t 7488 :file-handler t))) 7489 (set-process-query-on-exit-flag proc nil) 7490 (set-process-query-on-exit-flag (get-buffer-process stderr-buf) nil) 7491 (with-current-buffer (get-buffer stderr-buf) 7492 ;; Make the *NAME::stderr* buffer buffer-read-only, q to bury, etc. 7493 (special-mode)) 7494 (cons proc proc))))) 7495 :test? (or 7496 test-command 7497 (lambda () 7498 (lsp-server-present? (lsp-resolve-final-command command t)))))) 7499 7500 (defun lsp--open-network-stream (host port name) 7501 "Open network stream to HOST:PORT. 7502 NAME will be passed to `open-network-stream'. 7503 RETRY-COUNT is the number of the retries. 7504 SLEEP-INTERVAL is the sleep interval between each retry." 7505 (let* ((retries 0) 7506 (sleep-interval 0.01) 7507 (number-of-retries (/ lsp-tcp-connection-timeout sleep-interval)) 7508 connection) 7509 (while (and (not connection) (< retries number-of-retries)) 7510 (condition-case err 7511 (setq connection (open-network-stream name nil host port 7512 :type 'plain 7513 :coding 'no-conversion)) 7514 (file-error 7515 (let ((inhibit-message t)) 7516 (lsp--warn "Failed to connect to %s:%s with error message %s" 7517 host 7518 port 7519 (error-message-string err)) 7520 (sleep-for sleep-interval) 7521 (cl-incf retries))))) 7522 (or connection (error "Port %s was never taken. Consider increasing `lsp-tcp-connection-timeout'." port)))) 7523 7524 (defun lsp--port-available (host port) 7525 "Return non-nil if HOST and PORT are available." 7526 (condition-case _err 7527 (delete-process (open-network-stream "*connection-test*" nil host port :type 'plain)) 7528 (file-error t))) 7529 7530 (defun lsp--find-available-port (host starting-port) 7531 "Find available port on HOST starting from STARTING-PORT." 7532 (let ((port starting-port)) 7533 (while (not (lsp--port-available host port)) 7534 (cl-incf port)) 7535 port)) 7536 7537 (defun lsp-tcp-connection (command-fn) 7538 "Returns a connection property list similar to `lsp-stdio-connection'. 7539 COMMAND-FN can only be a function that takes a single argument, a 7540 port number. It should return a command for launches a language server 7541 process listening for TCP connections on the provided port." 7542 (cl-check-type command-fn function) 7543 (list 7544 :connect (lambda (filter sentinel name environment-fn _workspace) 7545 (let* ((host "localhost") 7546 (port (lsp--find-available-port host (cl-incf lsp--tcp-port))) 7547 (command (funcall command-fn port)) 7548 (final-command (if (consp command) command (list command))) 7549 (_ (unless (lsp-server-present? final-command) 7550 (user-error (format "Couldn't find executable %s" (cl-first final-command))))) 7551 (process-environment 7552 (lsp--compute-process-environment environment-fn)) 7553 (proc (make-process :name name :connection-type 'pipe :coding 'no-conversion 7554 :command final-command :sentinel sentinel :stderr (format "*%s::stderr*" name) :noquery t)) 7555 (tcp-proc (lsp--open-network-stream host port (concat name "::tcp")))) 7556 7557 ;; TODO: Same :noquery issue (see above) 7558 (set-process-query-on-exit-flag proc nil) 7559 (set-process-query-on-exit-flag tcp-proc nil) 7560 (set-process-filter tcp-proc filter) 7561 (cons tcp-proc proc))) 7562 :test? (lambda () (lsp-server-present? (funcall command-fn 0))))) 7563 7564 (defalias 'lsp-tcp-server 'lsp-tcp-server-command) 7565 7566 (defun lsp-tcp-server-command (command-fn) 7567 "Create tcp server connection. 7568 In this mode Emacs is TCP server and the language server connects 7569 to it. COMMAND is function with one parameter(the port) and it 7570 should return the command to start the LS server." 7571 (cl-check-type command-fn function) 7572 (list 7573 :connect (lambda (filter sentinel name environment-fn _workspace) 7574 (let* (tcp-client-connection 7575 (tcp-server (make-network-process :name (format "*tcp-server-%s*" name) 7576 :buffer (format "*tcp-server-%s*" name) 7577 :family 'ipv4 7578 :service lsp--tcp-server-port 7579 :sentinel (lambda (proc _string) 7580 (lsp-log "Language server %s is connected." name) 7581 (setf tcp-client-connection proc)) 7582 :server 't)) 7583 (port (process-contact tcp-server :service)) 7584 (final-command (funcall command-fn port)) 7585 (process-environment 7586 (lsp--compute-process-environment environment-fn)) 7587 (cmd-proc (make-process :name name 7588 :connection-type 'pipe 7589 :coding 'no-conversion 7590 :command final-command 7591 :stderr (format "*tcp-server-%s*::stderr" name) 7592 :noquery t))) 7593 (let ((retries 0)) 7594 ;; wait for the client to connect (we sit-for 500 ms, so have to double lsp--tcp-server-wait-seconds) 7595 (while (and (not tcp-client-connection) (< retries (* 2 lsp--tcp-server-wait-seconds))) 7596 (lsp--info "Waiting for connection for %s, retries: %s" name retries) 7597 (sit-for 0.500) 7598 (cl-incf retries))) 7599 7600 (unless tcp-client-connection 7601 (condition-case nil (delete-process tcp-server) (error)) 7602 (condition-case nil (delete-process cmd-proc) (error)) 7603 (error "Failed to create connection to %s on port %s" name port)) 7604 (lsp--info "Successfully connected to %s" name) 7605 7606 (set-process-query-on-exit-flag cmd-proc nil) 7607 (set-process-query-on-exit-flag tcp-client-connection nil) 7608 (set-process-query-on-exit-flag tcp-server nil) 7609 7610 (set-process-filter tcp-client-connection filter) 7611 (set-process-sentinel tcp-client-connection sentinel) 7612 (cons tcp-client-connection cmd-proc))) 7613 :test? (lambda () (lsp-server-present? (funcall command-fn 0))))) 7614 7615 (defalias 'lsp-tramp-connection 'lsp-stdio-connection) 7616 7617 (defun lsp--auto-configure () 7618 "Autoconfigure `company', `flycheck', `lsp-ui', etc if they are installed." 7619 (when (functionp 'lsp-ui-mode) 7620 (lsp-ui-mode)) 7621 7622 (if lsp-headerline-breadcrumb-enable 7623 (add-hook 'lsp-configure-hook 'lsp-headerline-breadcrumb-mode) 7624 (remove-hook 'lsp-configure-hook 'lsp-headerline-breadcrumb-mode)) 7625 (if lsp-modeline-code-actions-enable 7626 (add-hook 'lsp-configure-hook 'lsp-modeline-code-actions-mode) 7627 (remove-hook 'lsp-configure-hook 'lsp-modeline-code-actions-mode)) 7628 (if lsp-modeline-diagnostics-enable 7629 (add-hook 'lsp-configure-hook 'lsp-modeline-diagnostics-mode) 7630 (remove-hook 'lsp-configure-hook 'lsp-modeline-diagnostics-mode)) 7631 (if lsp-modeline-workspace-status-enable 7632 (add-hook 'lsp-configure-hook 'lsp-modeline-workspace-status-mode) 7633 (remove-hook 'lsp-configure-hook 'lsp-modeline-workspace-status-mode)) 7634 (if lsp-lens-enable 7635 (add-hook 'lsp-configure-hook 'lsp-lens--enable) 7636 (remove-hook 'lsp-configure-hook 'lsp-lens--enable)) 7637 (if lsp-semantic-tokens-enable 7638 (add-hook 'lsp-configure-hook 'lsp-semantic-tokens--enable) 7639 (remove-hook 'lsp-configure-hook 'lsp-semantic-tokens--enable)) 7640 7641 ;; yas-snippet config 7642 (setq-local yas-inhibit-overlay-modification-protection t)) 7643 7644 (defun lsp--restart-if-needed (workspace) 7645 "Handler restart for WORKSPACE." 7646 (when (or (eq lsp-restart 'auto-restart) 7647 (eq (lsp--workspace-shutdown-action workspace) 'restart) 7648 (and (eq lsp-restart 'interactive) 7649 (let ((query (format 7650 "Server %s exited (check corresponding stderr buffer for details). Do you want to restart it?" 7651 (lsp--workspace-print workspace)))) 7652 (y-or-n-p query)))) 7653 (--each (lsp--workspace-buffers workspace) 7654 (when (lsp-buffer-live-p it) 7655 (lsp-with-current-buffer it 7656 (if lsp--buffer-deferred 7657 (lsp-deferred) 7658 (lsp--info "Restarting LSP in buffer %s" (buffer-name)) 7659 (lsp))))))) 7660 7661 (defun lsp--update-key (table key fn) 7662 "Apply FN on value corresponding to KEY in TABLE." 7663 (let ((existing-value (gethash key table))) 7664 (if-let ((new-value (funcall fn existing-value))) 7665 (puthash key new-value table) 7666 (remhash key table)))) 7667 7668 (defun lsp--process-sentinel (workspace process exit-str) 7669 "Create the sentinel for WORKSPACE." 7670 (unless (process-live-p process) 7671 (lsp--handle-process-exit workspace exit-str))) 7672 7673 (defun lsp--handle-process-exit (workspace exit-str) 7674 (let* ((folder->workspaces (lsp-session-folder->servers (lsp-session))) 7675 (proc (lsp--workspace-proc workspace))) 7676 (lsp--warn "%s has exited (%s)" 7677 (lsp-process-name proc) 7678 (string-trim-right (or exit-str ""))) 7679 (with-lsp-workspace workspace 7680 ;; Clean workspace related data in each of the buffers 7681 ;; in the workspace. 7682 (--each (lsp--workspace-buffers workspace) 7683 (when (lsp-buffer-live-p it) 7684 (lsp-with-current-buffer it 7685 (setq lsp--buffer-workspaces (delete workspace lsp--buffer-workspaces)) 7686 (lsp--uninitialize-workspace) 7687 (lsp--spinner-stop) 7688 (lsp--remove-overlays 'lsp-highlight)))) 7689 7690 ;; Cleanup session from references to the closed workspace. 7691 (--each (hash-table-keys folder->workspaces) 7692 (lsp--update-key folder->workspaces it (apply-partially 'delete workspace))) 7693 7694 (lsp-process-cleanup proc)) 7695 7696 (run-hook-with-args 'lsp-after-uninitialized-functions workspace) 7697 7698 (if (eq (lsp--workspace-shutdown-action workspace) 'shutdown) 7699 (lsp--info "Workspace %s shutdown." (lsp--workspace-print workspace)) 7700 (lsp--restart-if-needed workspace)) 7701 (lsp--cleanup-hanging-watches))) 7702 7703 (defun lsp-workspace-folders (workspace) 7704 "Return all folders associated with WORKSPACE." 7705 (let (result) 7706 (->> (lsp-session) 7707 (lsp-session-folder->servers) 7708 (maphash (lambda (folder workspaces) 7709 (when (-contains? workspaces workspace) 7710 (push folder result))))) 7711 result)) 7712 7713 (defun lsp--start-workspace (session client-template root &optional initialization-options) 7714 "Create new workspace for CLIENT-TEMPLATE with project root ROOT. 7715 INITIALIZATION-OPTIONS are passed to initialize function. 7716 SESSION is the active session." 7717 (lsp--spinner-start) 7718 (-let* ((default-directory root) 7719 (client (copy-lsp--client client-template)) 7720 (workspace (make-lsp--workspace 7721 :root root 7722 :client client 7723 :status 'starting 7724 :buffers (list (lsp-current-buffer)) 7725 :host-root (file-remote-p root))) 7726 ((&lsp-cln 'server-id 'environment-fn 'new-connection 'custom-capabilities 7727 'multi-root 'initialized-fn) client) 7728 ((proc . cmd-proc) (funcall 7729 (or (plist-get new-connection :connect) 7730 (user-error "Client %s is configured incorrectly" client)) 7731 (lsp--create-filter-function workspace) 7732 (apply-partially #'lsp--process-sentinel workspace) 7733 (format "%s" server-id) 7734 environment-fn 7735 workspace)) 7736 (workspace-folders (gethash server-id (lsp-session-server-id->folders session)))) 7737 (setf (lsp--workspace-proc workspace) proc 7738 (lsp--workspace-cmd-proc workspace) cmd-proc) 7739 7740 ;; update (lsp-session-folder->servers) depending on whether we are starting 7741 ;; multi/single folder workspace 7742 (mapc (lambda (project-root) 7743 (->> session 7744 (lsp-session-folder->servers) 7745 (gethash project-root) 7746 (cl-pushnew workspace))) 7747 (or workspace-folders (list root))) 7748 7749 (with-lsp-workspace workspace 7750 (run-hooks 'lsp-before-initialize-hook) 7751 (lsp-request-async 7752 "initialize" 7753 (append 7754 (list :processId (unless (file-remote-p (buffer-file-name)) 7755 (emacs-pid)) 7756 :rootPath (lsp-file-local-name (expand-file-name root)) 7757 :clientInfo (list :name "emacs" 7758 :version (emacs-version)) 7759 :rootUri (lsp--path-to-uri root) 7760 :capabilities (lsp--client-capabilities custom-capabilities) 7761 :initializationOptions initialization-options 7762 :workDoneToken "1") 7763 (when lsp-server-trace 7764 (list :trace lsp-server-trace)) 7765 (when multi-root 7766 (->> workspace-folders 7767 (-distinct) 7768 (-map (lambda (folder) 7769 (list :uri (lsp--path-to-uri folder) 7770 :name (f-filename folder)))) 7771 (apply 'vector) 7772 (list :workspaceFolders)))) 7773 (-lambda ((&InitializeResult :capabilities)) 7774 ;; we know that Rust Analyzer will send {} which will be parsed as null 7775 ;; when using plists 7776 (when (equal 'rust-analyzer server-id) 7777 (-> capabilities 7778 (lsp:server-capabilities-text-document-sync?) 7779 (lsp:set-text-document-sync-options-save? t))) 7780 7781 (setf (lsp--workspace-server-capabilities workspace) capabilities 7782 (lsp--workspace-status workspace) 'initialized) 7783 7784 (with-lsp-workspace workspace 7785 (lsp-notify "initialized" lsp--empty-ht)) 7786 7787 (when initialized-fn (funcall initialized-fn workspace)) 7788 7789 (cl-callf2 -filter #'lsp-buffer-live-p (lsp--workspace-buffers workspace)) 7790 (->> workspace 7791 (lsp--workspace-buffers) 7792 (mapc (lambda (buffer) 7793 (lsp-with-current-buffer buffer 7794 (lsp--open-in-workspace workspace))))) 7795 7796 (with-lsp-workspace workspace 7797 (run-hooks 'lsp-after-initialize-hook)) 7798 (lsp--info "%s initialized successfully in folders: %s" 7799 (lsp--workspace-print workspace) 7800 (lsp-workspace-folders workspace))) 7801 :mode 'detached)) 7802 workspace)) 7803 7804 (defun lsp--load-default-session () 7805 "Load default session." 7806 (setq lsp--session (or (condition-case err 7807 (lsp--read-from-file lsp-session-file) 7808 (error (lsp--error "Failed to parse the session %s, starting with clean one." 7809 (error-message-string err)) 7810 nil)) 7811 (make-lsp-session)))) 7812 7813 (defun lsp-session () 7814 "Get the session associated with the current buffer." 7815 (or lsp--session (setq lsp--session (lsp--load-default-session)))) 7816 7817 (defun lsp--client-disabled-p (buffer-major-mode client) 7818 (seq-some 7819 (lambda (entry) 7820 (pcase entry 7821 ((pred symbolp) (eq entry client)) 7822 (`(,mode . ,client-or-list) 7823 (and (eq mode buffer-major-mode) 7824 (if (listp client-or-list) 7825 (memq client client-or-list) 7826 (eq client client-or-list)))))) 7827 lsp-disabled-clients)) 7828 7829 7830 ;; download server 7831 7832 (defcustom lsp-server-install-dir (expand-file-name 7833 (locate-user-emacs-file (f-join ".cache" "lsp"))) 7834 "Directory in which the servers will be installed." 7835 :risky t 7836 :type 'directory 7837 :package-version '(lsp-mode . "6.3") 7838 :group 'lsp-mode) 7839 7840 (defcustom lsp-verify-signature t 7841 "Whether to check GPG signatures of downloaded files." 7842 :type 'boolean 7843 :package-version '(lsp-mode . "8.0.0") 7844 :group 'lsp-mode) 7845 7846 (defvar lsp--dependencies (ht)) 7847 7848 (defun lsp-dependency (name &rest definitions) 7849 "Used to specify a language server DEPENDENCY, the server 7850 executable or other required file path. Typically, the 7851 DEPENDENCY is found by locating it on the system path using 7852 `executable-find'. 7853 7854 You can explicitly call lsp-dependency in your environment to 7855 specify the absolute path to the DEPENDENCY. For example, the 7856 typescript-language-server requires both the server and the 7857 typescript compiler. If you have installed them in a team shared 7858 read-only location, you can instruct lsp-mode to use them via 7859 7860 (eval-after-load `lsp-mode 7861 `(progn 7862 (require lsp-javascript) 7863 (lsp-dependency typescript-language-server (:system ,tls-exe)) 7864 (lsp-dependency typescript (:system ,ts-js)))) 7865 7866 where tls-exe is the absolute path to the typescript-language-server 7867 executable and ts-js is the absolute path to the typescript compiler 7868 JavaScript file, tsserver.js (the *.js is required for Windows)." 7869 (ht-set lsp--dependencies name definitions)) 7870 7871 (defun lsp--server-binary-present? (client) 7872 (unless (equal (lsp--client-server-id client) 'lsp-pwsh) 7873 (condition-case () 7874 (-some-> client lsp--client-new-connection (plist-get :test?) funcall) 7875 (error nil) 7876 (args-out-of-range nil)))) 7877 7878 (define-minor-mode lsp-installation-buffer-mode 7879 "Mode used in *lsp-installation* buffers. 7880 It can be used to set-up keybindings, etc. Disabling this mode 7881 detaches the installation buffer from commands like 7882 `lsp-select-installation-buffer'." 7883 :init-value nil 7884 :lighter nil) 7885 7886 (defface lsp-installation-finished-buffer-face '((t :foreground "orange")) 7887 "Face used for finished installation buffers. 7888 Used in `lsp-select-installation-buffer'." 7889 :group 'lsp-mode) 7890 7891 (defface lsp-installation-buffer-face '((t :foreground "green")) 7892 "Face used for installation buffers still in progress. 7893 Used in `lsp-select-installation-buffer'." 7894 :group 'lsp-mode) 7895 7896 (defun lsp--installation-buffer? (buf) 7897 "Check whether BUF is an `lsp-async-start-process' buffer." 7898 (buffer-local-value 'lsp-installation-buffer-mode buf)) 7899 7900 (defun lsp-select-installation-buffer (&optional show-finished) 7901 "Interactively choose an installation buffer. 7902 If SHOW-FINISHED is set, leftover (finished) installation buffers 7903 are still shown." 7904 (interactive "P") 7905 (let ((bufs (--filter (and (lsp--installation-buffer? it) 7906 (or show-finished (get-buffer-process it))) 7907 (buffer-list)))) 7908 (pcase bufs 7909 (`nil (user-error "No installation buffers")) 7910 (`(,buf) (pop-to-buffer buf)) 7911 (bufs (pop-to-buffer (completing-read "Select installation buffer: " 7912 (--map (propertize (buffer-name it) 'face 7913 (if (get-buffer-process it) 7914 'lsp-installation-buffer-face 7915 'lsp-installation-finished-buffer-face)) 7916 bufs))))))) 7917 7918 (defun lsp-cleanup-installation-buffers () 7919 "Delete finished *lsp-installation* buffers." 7920 (interactive) 7921 (dolist (buf (buffer-list)) 7922 (when (and (lsp--installation-buffer? buf) (not (get-buffer-process buf))) 7923 (kill-buffer buf)))) 7924 7925 (defun lsp--download-status () 7926 (-some--> #'lsp--client-download-in-progress? 7927 (lsp--filter-clients it) 7928 (-map (-compose #'symbol-name #'lsp--client-server-id) it) 7929 (format "%s" it) 7930 (propertize it 'face 'success) 7931 (format " Installing following servers: %s" it) 7932 (propertize it 7933 'local-map (make-mode-line-mouse-map 7934 'mouse-1 #'lsp-select-installation-buffer) 7935 'mouse-face 'highlight))) 7936 7937 (defun lsp--install-server-internal (client &optional update?) 7938 (unless (lsp--client-download-server-fn client) 7939 (user-error "There is no automatic installation for `%s', you have to install it manually following lsp-mode's documentation." 7940 (lsp--client-server-id client))) 7941 7942 (setf (lsp--client-download-in-progress? client) t) 7943 (add-to-list 'global-mode-string '(t (:eval (lsp--download-status)))) 7944 (cl-flet ((done 7945 (success? &optional error-message) 7946 ;; run with idle timer to make sure the lsp command is executed in 7947 ;; the main thread, see #2739. 7948 (run-with-timer 7949 0.0 7950 nil 7951 (lambda () 7952 (-let [(&lsp-cln 'server-id 'buffers) client] 7953 (setf (lsp--client-download-in-progress? client) nil 7954 (lsp--client-buffers client) nil) 7955 (if success? 7956 (lsp--info "Server %s downloaded, auto-starting in %s buffers." server-id 7957 (length buffers)) 7958 (lsp--error "Server %s install process failed with the following error message: %s. 7959 Check `*lsp-install*' and `*lsp-log*' buffer." 7960 server-id 7961 error-message)) 7962 (seq-do 7963 (lambda (buffer) 7964 (when (lsp-buffer-live-p buffer) 7965 (lsp-with-current-buffer buffer 7966 (cl-callf2 -remove-item '(t (:eval (lsp--download-status))) 7967 global-mode-string) 7968 (when success? (lsp))))) 7969 buffers) 7970 (unless (lsp--filter-clients #'lsp--client-download-in-progress?) 7971 (cl-callf2 -remove-item '(t (:eval (lsp--download-status))) 7972 global-mode-string))))))) 7973 (lsp--info "Download %s started." (lsp--client-server-id client)) 7974 (condition-case err 7975 (funcall 7976 (lsp--client-download-server-fn client) 7977 client 7978 (lambda () (done t)) 7979 (lambda (msg) (done nil msg)) 7980 update?) 7981 (error 7982 (done nil (error-message-string err)))))) 7983 7984 (defun lsp--require-packages () 7985 "Load `lsp-client-packages' if needed." 7986 (when (and lsp-auto-configure (not lsp--client-packages-required)) 7987 (seq-do (lambda (package) 7988 ;; loading client is slow and `lsp' can be called repeatedly 7989 (unless (featurep package) 7990 (require package nil t))) 7991 lsp-client-packages) 7992 (setq lsp--client-packages-required t))) 7993 7994 ;;;###autoload 7995 (defun lsp-install-server (update? &optional server-id) 7996 "Interactively install or re-install server. 7997 When prefix UPDATE? is t force installation even if the server is present." 7998 (interactive "P") 7999 (lsp--require-packages) 8000 (let* ((chosen-client (or (gethash server-id lsp-clients) 8001 (lsp--completing-read 8002 "Select server to install/re-install: " 8003 (or (->> lsp-clients 8004 (ht-values) 8005 (-filter (-andfn 8006 (-not #'lsp--client-download-in-progress?) 8007 #'lsp--client-download-server-fn))) 8008 (user-error "There are no servers with automatic installation")) 8009 (lambda (client) 8010 (let ((server-name (-> client lsp--client-server-id symbol-name))) 8011 (if (lsp--server-binary-present? client) 8012 (concat server-name " (Already installed)") 8013 server-name))) 8014 nil 8015 t))) 8016 (update? (or update? 8017 (and (not (lsp--client-download-in-progress? chosen-client)) 8018 (lsp--server-binary-present? chosen-client))))) 8019 (lsp--install-server-internal chosen-client update?))) 8020 8021 ;;;###autoload 8022 (defun lsp-uninstall-server (dir) 8023 "Delete a LSP server from `lsp-server-install-dir'." 8024 (interactive 8025 (list (read-directory-name "Uninstall LSP server: " (f-slash lsp-server-install-dir)))) 8026 (unless (file-directory-p dir) 8027 (user-error "Couldn't find %s directory" dir)) 8028 (delete-directory dir 'recursive) 8029 (message "Server `%s' uninstalled." (file-name-nondirectory (directory-file-name dir)))) 8030 8031 ;;;###autoload 8032 (defun lsp-uninstall-servers () 8033 "Uninstall all installed servers." 8034 (interactive) 8035 (let* ((dir lsp-server-install-dir) 8036 (servers (ignore-errors 8037 (directory-files dir t 8038 directory-files-no-dot-files-regexp)))) 8039 (if (or (not (file-directory-p dir)) (zerop (length servers))) 8040 (user-error "No servers to uninstall") 8041 (when (yes-or-no-p 8042 (format "Servers to uninstall: %d (%s), proceed? " 8043 (length servers) 8044 (mapconcat (lambda (server) 8045 (file-name-nondirectory (directory-file-name server))) 8046 servers " "))) 8047 (mapc #'lsp-uninstall-server servers) 8048 (message "All servers uninstalled"))))) 8049 8050 ;;;###autoload 8051 (defun lsp-update-server (&optional server-id) 8052 "Interactively update (reinstall) a server." 8053 (interactive) 8054 (lsp--require-packages) 8055 (let ((chosen-client (or (gethash server-id lsp-clients) 8056 (lsp--completing-read 8057 "Select server to update (if not on the list, probably you need to `lsp-install-server`): " 8058 (or (->> lsp-clients 8059 (ht-values) 8060 (-filter (-andfn 8061 (-not #'lsp--client-download-in-progress?) 8062 #'lsp--client-download-server-fn 8063 #'lsp--server-binary-present?))) 8064 (user-error "There are no servers to update")) 8065 (lambda (client) 8066 (-> client lsp--client-server-id symbol-name)) 8067 nil 8068 t)))) 8069 (lsp--install-server-internal chosen-client t))) 8070 8071 ;;;###autoload 8072 (defun lsp-update-servers () 8073 "Update (reinstall) all installed servers." 8074 (interactive) 8075 (lsp--require-packages) 8076 (mapc (lambda (client) (lsp--install-server-internal client t)) 8077 (-filter (-andfn 8078 (-not #'lsp--client-download-in-progress?) 8079 #'lsp--client-download-server-fn 8080 #'lsp--server-binary-present?) (hash-table-values lsp-clients)))) 8081 8082 ;;;###autoload 8083 (defun lsp-ensure-server (server-id) 8084 "Ensure server SERVER-ID" 8085 (lsp--require-packages) 8086 (if-let ((client (gethash server-id lsp-clients))) 8087 (unless (lsp--server-binary-present? client) 8088 (lsp--info "Server `%s' is not preset, installing..." server-id) 8089 (lsp-install-server nil server-id)) 8090 (warn "Unable to find server registration with id %s" server-id))) 8091 8092 (defun lsp-async-start-process (callback error-callback &rest command) 8093 "Start async process COMMAND with CALLBACK and ERROR-CALLBACK." 8094 (let ((name (cl-first command))) 8095 (with-current-buffer (compilation-start (mapconcat #'shell-quote-argument (-filter (lambda (cmd) 8096 (not (null cmd))) 8097 command) 8098 " ") t 8099 (lambda (&rest _) 8100 (generate-new-buffer-name (format "*lsp-install: %s*" name)))) 8101 (lsp-installation-buffer-mode +1) 8102 (view-mode +1) 8103 (add-hook 8104 'compilation-finish-functions 8105 (lambda (_buf status) 8106 (if (string= "finished\n" status) 8107 (condition-case err 8108 (funcall callback) 8109 (error 8110 (funcall error-callback (error-message-string err)))) 8111 (funcall error-callback (s-trim-right status)))) 8112 nil t)))) 8113 8114 (defun lsp-resolve-value (value) 8115 "Resolve VALUE's value. 8116 If it is function - call it. 8117 If it is a variable - return it's value 8118 Otherwise returns value itself." 8119 (cond 8120 ((functionp value) (funcall value)) 8121 ((and (symbolp value) (boundp value)) (symbol-value value)) 8122 (value))) 8123 8124 (defvar lsp-deps-providers 8125 (list :npm (list :path #'lsp--npm-dependency-path 8126 :install #'lsp--npm-dependency-install) 8127 :cargo (list :path #'lsp--cargo-dependency-path 8128 :install #'lsp--cargo-dependency-install) 8129 :system (list :path #'lsp--system-path) 8130 :download (list :path #'lsp-download-path 8131 :install #'lsp-download-install))) 8132 8133 (defun lsp--system-path (path) 8134 "If PATH is absolute and exists return it as is. Otherwise, 8135 return the absolute path to the executable defined by PATH or 8136 nil." 8137 ;; For node.js 'sub-packages' PATH may point to a *.js file. Consider the 8138 ;; typescript-language-server. When lsp invokes the server, lsp needs to 8139 ;; supply the path to the typescript compiler, tsserver.js, as an argument. To 8140 ;; make code platform independent, one must pass the absolute path to the 8141 ;; tsserver.js file (Windows requires a *.js file - see help on the JavaScript 8142 ;; child process spawn command that is invoked by the 8143 ;; typescript-language-server). This is why we check for existence and not 8144 ;; that the path is executable. 8145 (let ((path (lsp-resolve-value path))) 8146 (cond 8147 ((and (f-absolute? path) 8148 (f-exists? path)) 8149 path) 8150 ((executable-find path t) path)))) 8151 8152 (defun lsp-package-path (dependency) 8153 "Path to the DEPENDENCY each of the registered providers." 8154 (let (path) 8155 (-first (-lambda ((provider . rest)) 8156 (setq path (-some-> lsp-deps-providers 8157 (plist-get provider) 8158 (plist-get :path) 8159 (apply rest)))) 8160 (gethash dependency lsp--dependencies)) 8161 path)) 8162 8163 (defun lsp-package-ensure (dependency callback error-callback) 8164 "Asynchronously ensure a package." 8165 (or (-first (-lambda ((provider . rest)) 8166 (-some-> lsp-deps-providers 8167 (plist-get provider) 8168 (plist-get :install) 8169 (apply (cl-list* callback error-callback rest)))) 8170 (gethash dependency lsp--dependencies)) 8171 (funcall error-callback (format "Unable to find a way to install %s" dependency)))) 8172 8173 8174 ;; npm handling 8175 8176 ;; https://docs.npmjs.com/files/folders#executables 8177 (cl-defun lsp--npm-dependency-path (&key package path &allow-other-keys) 8178 "Return npm dependency PATH for PACKAGE." 8179 (let ((path (executable-find 8180 (f-join lsp-server-install-dir "npm" package 8181 (cond ((eq system-type 'windows-nt) "") 8182 (t "bin")) 8183 path) 8184 t))) 8185 (unless (and path (f-exists? path)) 8186 (error "The package %s is not installed. Unable to find %s" package path)) 8187 path)) 8188 8189 (cl-defun lsp--npm-dependency-install (callback error-callback &key package &allow-other-keys) 8190 (if-let ((npm-binary (executable-find "npm"))) 8191 (progn 8192 ;; Explicitly `make-directory' to work around NPM bug in 8193 ;; versions 7.0.0 through 7.4.1. See 8194 ;; https://github.com/emacs-lsp/lsp-mode/issues/2364 for 8195 ;; discussion. 8196 (make-directory (f-join lsp-server-install-dir "npm" package "lib") 'parents) 8197 (lsp-async-start-process (lambda () 8198 (if (string-empty-p 8199 (string-trim (shell-command-to-string 8200 (mapconcat #'shell-quote-argument `(,npm-binary "view" ,package "peerDependencies") " ")))) 8201 (funcall callback) 8202 (let ((default-directory (f-dirname (car (last (directory-files-recursively (f-join lsp-server-install-dir "npm" package) "package.json"))))) 8203 (process-environment (append '("npm_config_yes=true") process-environment))) ;; Disable prompting for older versions of npx 8204 (when (f-dir-p default-directory) 8205 (lsp-async-start-process callback 8206 error-callback 8207 (executable-find "npx") 8208 "npm-install-peers"))))) 8209 error-callback 8210 npm-binary 8211 "-g" 8212 "--prefix" 8213 (f-join lsp-server-install-dir "npm" package) 8214 "install" 8215 package)) 8216 (lsp-log "Unable to install %s via `npm' because it is not present" package) 8217 nil)) 8218 8219 8220 ;; Cargo dependency handling 8221 (cl-defun lsp--cargo-dependency-path (&key package path &allow-other-keys) 8222 (let ((path (executable-find 8223 (f-join lsp-server-install-dir 8224 "cargo" 8225 package 8226 "bin" 8227 path) 8228 t))) 8229 (unless (and path (f-exists? path)) 8230 (error "The package %s is not installed. Unable to find %s" package path)) 8231 path)) 8232 8233 (cl-defun lsp--cargo-dependency-install (callback error-callback &key package git &allow-other-keys) 8234 (if-let ((cargo-binary (executable-find "cargo"))) 8235 (lsp-async-start-process 8236 callback 8237 error-callback 8238 cargo-binary 8239 "install" 8240 package 8241 (when git 8242 "--git") 8243 git 8244 "--root" 8245 (f-join lsp-server-install-dir "cargo" package)) 8246 (lsp-log "Unable to install %s via `cargo' because it is not present" package) 8247 nil)) 8248 8249 8250 8251 ;; Download URL handling 8252 (cl-defun lsp-download-install (callback error-callback &key url asc-url pgp-key store-path decompress &allow-other-keys) 8253 (let* ((url (lsp-resolve-value url)) 8254 (store-path (lsp-resolve-value store-path)) 8255 ;; (decompress (lsp-resolve-value decompress)) 8256 (download-path 8257 (pcase decompress 8258 (:gzip (concat store-path ".gz")) 8259 (:zip (concat store-path ".zip")) 8260 (:targz (concat store-path ".tar.gz")) 8261 (`nil store-path) 8262 (_ (error ":decompress must be `:gzip', `:zip', `:targz' or `nil'"))))) 8263 (make-thread 8264 (lambda () 8265 (condition-case err 8266 (progn 8267 (when (f-exists? download-path) 8268 (f-delete download-path)) 8269 (when (f-exists? store-path) 8270 (f-delete store-path)) 8271 (lsp--info "Starting to download %s to %s..." url download-path) 8272 (mkdir (f-parent download-path) t) 8273 (url-copy-file url download-path) 8274 (lsp--info "Finished downloading %s..." download-path) 8275 (when (and lsp-verify-signature asc-url pgp-key) 8276 (if (executable-find epg-gpg-program) 8277 (let ((asc-download-path (concat download-path ".asc")) 8278 (context (epg-make-context)) 8279 (fingerprint) 8280 (signature)) 8281 (when (f-exists? asc-download-path) 8282 (f-delete asc-download-path)) 8283 (lsp--info "Starting to download %s to %s..." asc-url asc-download-path) 8284 (url-copy-file asc-url asc-download-path) 8285 (lsp--info "Finished downloading %s..." asc-download-path) 8286 (epg-import-keys-from-string context pgp-key) 8287 (setq fingerprint (epg-import-status-fingerprint 8288 (car 8289 (epg-import-result-imports 8290 (epg-context-result-for context 'import))))) 8291 (lsp--info "Verifying signature %s..." asc-download-path) 8292 (epg-verify-file context asc-download-path download-path) 8293 (setq signature (car (epg-context-result-for context 'verify))) 8294 (unless (and 8295 (eq (epg-signature-status signature) 'good) 8296 (equal (epg-signature-fingerprint signature) fingerprint)) 8297 (error "Failed to verify GPG signature: %s" (epg-signature-to-string signature)))) 8298 (lsp--warn "GPG is not installed, skipping the signature check."))) 8299 (when decompress 8300 (lsp--info "Decompressing %s..." download-path) 8301 (pcase decompress 8302 (:gzip 8303 (lsp-gunzip download-path)) 8304 (:zip (lsp-unzip download-path (f-parent store-path))) 8305 (:targz (lsp-tar-gz-decompress download-path (f-parent store-path)))) 8306 (lsp--info "Decompressed %s..." store-path)) 8307 (funcall callback)) 8308 (error (funcall error-callback err))))))) 8309 8310 (cl-defun lsp-download-path (&key store-path binary-path set-executable? &allow-other-keys) 8311 "Download URL and store it into STORE-PATH. 8312 8313 SET-EXECUTABLE? when non-nil change the executable flags of 8314 STORE-PATH to make it executable. BINARY-PATH can be specified 8315 when the binary to start does not match the name of the 8316 archive (e.g. when the archive has multiple files)" 8317 (let ((store-path (or (lsp-resolve-value binary-path) 8318 (lsp-resolve-value store-path)))) 8319 (cond 8320 ((executable-find store-path) store-path) 8321 ((and set-executable? (f-exists? store-path)) 8322 (set-file-modes store-path #o0700) 8323 store-path) 8324 ((f-exists? store-path) store-path)))) 8325 8326 (defun lsp--find-latest-gh-release-url (url regex) 8327 "Fetch the latest version in the releases given by URL by using REGEX." 8328 (let ((url-request-method "GET")) 8329 (with-current-buffer (url-retrieve-synchronously url) 8330 (goto-char (point-min)) 8331 (re-search-forward "\n\n" nil 'noerror) 8332 (delete-region (point-min) (point)) 8333 (let* ((json-result (lsp-json-read-buffer))) 8334 (message "Latest version found: %s" (lsp-get json-result :tag_name)) 8335 (--> json-result 8336 (lsp-get it :assets) 8337 (seq-find (lambda (entry) (string-match-p regex (lsp-get entry :name))) it) 8338 (lsp-get it :browser_download_url)))))) 8339 8340 ;; unzip 8341 8342 (defconst lsp-ext-pwsh-script "powershell -noprofile -noninteractive \ 8343 -nologo -ex bypass -command Expand-Archive -path '%s' -dest '%s'" 8344 "Powershell script to unzip file.") 8345 8346 (defconst lsp-ext-unzip-script "bash -c 'mkdir -p %2$s && unzip -qq -o %1$s -d %2$s'" 8347 "Unzip script to unzip file.") 8348 8349 (defcustom lsp-unzip-script (lambda () 8350 (cond ((executable-find "unzip") lsp-ext-unzip-script) 8351 ((executable-find "powershell") lsp-ext-pwsh-script) 8352 (t nil))) 8353 "The script to unzip." 8354 :group 'lsp-mode 8355 :type 'string 8356 :package-version '(lsp-mode . "8.0.0")) 8357 8358 (defun lsp-unzip (zip-file dest) 8359 "Unzip ZIP-FILE to DEST." 8360 (unless lsp-unzip-script 8361 (error "Unable to find `unzip' or `powershell' on the path, please customize `lsp-unzip-script'")) 8362 (shell-command (format (lsp-resolve-value lsp-unzip-script) zip-file dest))) 8363 8364 ;; gunzip 8365 8366 (defconst lsp-ext-gunzip-script "gzip -d %1$s" 8367 "Script to decompress a gzippped file with gzip.") 8368 8369 (defcustom lsp-gunzip-script (lambda () 8370 (cond ((executable-find "gzip") lsp-ext-gunzip-script) 8371 (t nil))) 8372 "The script to decompress a gzipped file. 8373 Should be a format string with one argument for the file to be decompressed 8374 in place." 8375 :group 'lsp-mode 8376 :type 'string 8377 :package-version '(lsp-mode . "8.0.0")) 8378 8379 (defun lsp-gunzip (gz-file) 8380 "Decompress GZ-FILE in place." 8381 (unless lsp-gunzip-script 8382 (error "Unable to find `gzip' on the path, please either customize `lsp-gunzip-script' or manually decompress %s" gz-file)) 8383 (shell-command (format (lsp-resolve-value lsp-gunzip-script) gz-file))) 8384 8385 ;; tar.gz decompression 8386 8387 (defconst lsp-ext-tar-script "bash -c 'mkdir -p %2$s; tar xf %1$s --directory=%2$s'" 8388 "Script to decompress a .tar.gz file.") 8389 8390 (defcustom lsp-tar-script (lambda () 8391 (cond ((executable-find "tar") lsp-ext-tar-script) 8392 (t nil))) 8393 "The script to decompress a .tar.gz file. 8394 Should be a format string with one argument for the file to be decompressed 8395 in place." 8396 :group 'lsp-mode 8397 :type 'string) 8398 8399 (defun lsp-tar-gz-decompress (targz-file dest) 8400 "Decompress TARGZ-FILE in DEST." 8401 (unless lsp-tar-script 8402 (error "Unable to find `tar' on the path, please either customize `lsp-tar-script' or manually decompress %s" targz-file)) 8403 (shell-command (format (lsp-resolve-value lsp-tar-script) targz-file dest))) 8404 8405 8406 ;; VSCode marketplace 8407 8408 (defcustom lsp-vscode-ext-url 8409 "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/%s/vsextensions/%s/%s/vspackage%s" 8410 "Vscode extension template url." 8411 :group 'lsp-mode 8412 :type 'string 8413 :package-version '(lsp-mode . "8.0.0")) 8414 8415 (defun lsp-vscode-extension-url (publisher name version &optional targetPlatform) 8416 "Return the URL to vscode extension. 8417 PUBLISHER is the extension publisher. 8418 NAME is the name of the extension. 8419 VERSION is the version of the extension. 8420 TARGETPLATFORM is the targetPlatform of the extension." 8421 (format lsp-vscode-ext-url publisher name version (or targetPlatform ""))) 8422 8423 8424 8425 ;; Queueing prompts 8426 8427 (defvar lsp--question-queue nil 8428 "List of questions yet to be asked by `lsp-ask-question'.") 8429 8430 (defun lsp-ask-question (question options callback) 8431 "Prompt the user to answer the QUESTION with one of the OPTIONS from the 8432 minibuffer. Once the user selects an option, the CALLBACK function will be 8433 called, passing the selected option to it. 8434 8435 If the user is currently being shown a question, the question will be stored in 8436 `lsp--question-queue', and will be asked once the user has answered the current 8437 question." 8438 (add-to-list 'lsp--question-queue `(("question" . ,question) 8439 ("options" . ,options) 8440 ("callback" . ,callback)) t) 8441 (when (eq (length lsp--question-queue) 1) 8442 (lsp--process-question-queue))) 8443 8444 (defun lsp--process-question-queue () 8445 "Take the first question from `lsp--question-queue', process it, then process 8446 the next question until the queue is empty." 8447 (-let* (((&alist "question" "options" "callback") (car lsp--question-queue)) 8448 (answer (completing-read question options nil t))) 8449 (pop lsp--question-queue) 8450 (funcall callback answer) 8451 (when lsp--question-queue 8452 (lsp--process-question-queue)))) 8453 8454 (defun lsp--supports-buffer? (client) 8455 (and 8456 ;; both file and client remote or both local 8457 (eq (---truthy? (file-remote-p (buffer-file-name))) 8458 (---truthy? (lsp--client-remote? client))) 8459 8460 ;; activation function or major-mode match. 8461 (if-let ((activation-fn (lsp--client-activation-fn client))) 8462 (funcall activation-fn (buffer-file-name) major-mode) 8463 (-contains? (lsp--client-major-modes client) major-mode)) 8464 8465 ;; check whether it is enabled if `lsp-enabled-clients' is not null 8466 (or (null lsp-enabled-clients) 8467 (or (member (lsp--client-server-id client) lsp-enabled-clients) 8468 (ignore (lsp--info "Client %s is not in lsp-enabled-clients" 8469 (lsp--client-server-id client))))) 8470 8471 ;; check whether it is not disabled. 8472 (not (lsp--client-disabled-p major-mode (lsp--client-server-id client))))) 8473 8474 (defun lsp--filter-clients (pred) 8475 (->> lsp-clients hash-table-values (-filter pred))) 8476 8477 (defun lsp--find-clients () 8478 "Find clients which can handle current buffer." 8479 (-when-let (matching-clients (lsp--filter-clients (-andfn #'lsp--supports-buffer? 8480 #'lsp--server-binary-present?))) 8481 (lsp-log "Found the following clients for %s: %s" 8482 (buffer-file-name) 8483 (s-join ", " 8484 (-map (lambda (client) 8485 (format "(server-id %s, priority %s)" 8486 (lsp--client-server-id client) 8487 (lsp--client-priority client))) 8488 matching-clients))) 8489 (-let* (((add-on-clients main-clients) (-separate #'lsp--client-add-on? matching-clients)) 8490 (selected-clients (if-let ((main-client (and main-clients 8491 (--max-by (> (lsp--client-priority it) 8492 (lsp--client-priority other)) 8493 main-clients)))) 8494 (cons main-client add-on-clients) 8495 add-on-clients))) 8496 (lsp-log "The following clients were selected based on priority: %s" 8497 (s-join ", " 8498 (-map (lambda (client) 8499 (format "(server-id %s, priority %s)" 8500 (lsp--client-server-id client) 8501 (lsp--client-priority client))) 8502 selected-clients))) 8503 selected-clients))) 8504 8505 (defun lsp-workspace-remove-all-folders() 8506 "Delete all lsp tracked folders." 8507 (interactive) 8508 (--each (lsp-session-folders (lsp-session)) 8509 (lsp-workspace-folders-remove it))) 8510 8511 (defun lsp-register-client (client) 8512 "Registers LSP client CLIENT." 8513 (let ((client-id (lsp--client-server-id client))) 8514 (puthash client-id client lsp-clients) 8515 (setplist (intern (format "lsp-%s-after-open-hook" client-id)) 8516 `( standard-value (nil) custom-type hook 8517 custom-package-version (lsp-mode . "7.0.1") 8518 variable-documentation ,(format "Hooks to run after `%s' server is run." client-id) 8519 custom-requests nil))) 8520 (when (and lsp-auto-register-remote-clients 8521 (not (lsp--client-remote? client))) 8522 (let ((remote-client (copy-lsp--client client))) 8523 (setf (lsp--client-remote? remote-client) t 8524 (lsp--client-server-id remote-client) (intern 8525 (format "%s-tramp" 8526 (lsp--client-server-id client))) 8527 ;; disable automatic download 8528 (lsp--client-download-server-fn remote-client) nil) 8529 (lsp-register-client remote-client)))) 8530 8531 (defun lsp--create-initialization-options (_session client) 8532 "Create initialization-options from SESSION and CLIENT. 8533 Add workspace folders depending on server being multiroot and 8534 session workspace folder configuration for the server." 8535 (let* ((initialization-options-or-fn (lsp--client-initialization-options client))) 8536 (if (functionp initialization-options-or-fn) 8537 (funcall initialization-options-or-fn) 8538 initialization-options-or-fn))) 8539 8540 (defvar lsp-client-settings (make-hash-table :test 'equal) 8541 "For internal use, any external users please use 8542 `lsp-register-custom-settings' function instead") 8543 8544 (defun lsp-register-custom-settings (props) 8545 "Register PROPS. 8546 PROPS is list of triple (path value boolean?) where PATH is the path to the 8547 property; VALUE can be a literal value, symbol to be evaluated, or either a 8548 function or lambda function to be called without arguments; BOOLEAN? is an 8549 optional flag that should be non-nil for boolean settings, when it is nil the 8550 property will be ignored if the VALUE is nil. 8551 8552 Example: `(lsp-register-custom-settings `((\"foo.bar.buzz.enabled\" t t)))' 8553 \(note the double parentheses)" 8554 (mapc 8555 (-lambda ((path . rest)) 8556 (puthash path rest lsp-client-settings)) 8557 props)) 8558 8559 (defun lsp-region-text (region) 8560 "Get the text for REGION in current buffer." 8561 (-let (((start . end) (lsp--range-to-region region))) 8562 (buffer-substring-no-properties start end))) 8563 8564 (defun lsp-ht-set (tbl paths value) 8565 "Set nested hash table value. 8566 TBL - a hash table, PATHS is the path to the nested VALUE." 8567 (pcase paths 8568 (`(,path) (ht-set! tbl path value)) 8569 (`(,path . ,rst) (let ((nested-tbl (or (gethash path tbl) 8570 (let ((temp-tbl (ht))) 8571 (ht-set! tbl path temp-tbl) 8572 temp-tbl)))) 8573 (lsp-ht-set nested-tbl rst value))))) 8574 8575 ;; sections 8576 8577 (defalias 'defcustom-lsp 'lsp-defcustom) 8578 8579 (defmacro lsp-defcustom (symbol standard doc &rest args) 8580 "Defines `lsp-mode' server property." 8581 (declare (doc-string 3) (debug (name body)) 8582 (indent defun)) 8583 (let ((path (plist-get args :lsp-path))) 8584 (cl-remf args :lsp-path) 8585 `(progn 8586 (lsp-register-custom-settings 8587 (quote ((,path ,symbol ,(equal ''boolean (plist-get args :type)))))) 8588 8589 (defcustom ,symbol ,standard ,doc 8590 :set (lambda (sym val) 8591 (lsp--set-custom-property sym val ,path)) 8592 ,@args)))) 8593 8594 (defun lsp--set-custom-property (sym val path) 8595 (set sym val) 8596 (let ((section (cl-first (s-split "\\." path)))) 8597 (mapc (lambda (workspace) 8598 (when (-contains? (lsp--client-synchronize-sections (lsp--workspace-client workspace)) 8599 section) 8600 (with-lsp-workspace workspace 8601 (lsp--set-configuration (lsp-configuration-section section))))) 8602 (lsp--session-workspaces (lsp-session))))) 8603 8604 (defun lsp-configuration-section (section) 8605 "Get settings for SECTION." 8606 (let ((ret (ht-create))) 8607 (maphash (-lambda (path (variable boolean?)) 8608 (when (s-matches? (concat (regexp-quote section) "\\..*") path) 8609 (let* ((symbol-value (-> variable 8610 lsp-resolve-value 8611 lsp-resolve-value)) 8612 (value (if (and boolean? (not symbol-value)) 8613 :json-false 8614 symbol-value))) 8615 (when (or boolean? value) 8616 (lsp-ht-set ret (s-split "\\." path) value))))) 8617 lsp-client-settings) 8618 ret)) 8619 8620 8621 (defun lsp--start-connection (session client project-root) 8622 "Initiates connection created from CLIENT for PROJECT-ROOT. 8623 SESSION is the active session." 8624 (when (lsp--client-multi-root client) 8625 (cl-pushnew project-root (gethash (lsp--client-server-id client) 8626 (lsp-session-server-id->folders session)))) 8627 (run-hook-with-args 'lsp-workspace-folders-changed-functions (list project-root) nil) 8628 8629 (unwind-protect 8630 (lsp--start-workspace session client project-root (lsp--create-initialization-options session client)) 8631 (lsp--spinner-stop))) 8632 8633 ;; lsp-log-io-mode 8634 8635 (defvar lsp-log-io-mode-map 8636 (let ((map (make-sparse-keymap))) 8637 (define-key map (kbd "M-n") #'lsp-log-io-next) 8638 (define-key map (kbd "M-p") #'lsp-log-io-prev) 8639 (define-key map (kbd "k") #'lsp--erase-log-buffer) 8640 (define-key map (kbd "K") #'lsp--erase-session-log-buffers) 8641 map) 8642 "Keymap for lsp log buffer mode.") 8643 8644 (define-derived-mode lsp-log-io-mode special-mode "LspLogIo" 8645 "Special mode for viewing IO logs.") 8646 8647 (defun lsp-workspace-show-log (workspace) 8648 "Display the log buffer of WORKSPACE." 8649 (interactive 8650 (list (if lsp-log-io 8651 (if (eq (length (lsp-workspaces)) 1) 8652 (cl-first (lsp-workspaces)) 8653 (lsp--completing-read "Workspace: " (lsp-workspaces) 8654 #'lsp--workspace-print nil t)) 8655 (user-error "IO logging is disabled")))) 8656 (pop-to-buffer (lsp--get-log-buffer-create workspace))) 8657 8658 (defalias 'lsp-switch-to-io-log-buffer 'lsp-workspace-show-log) 8659 8660 (defun lsp--get-log-buffer-create (workspace) 8661 "Return the lsp log buffer of WORKSPACE, creating a new one if needed." 8662 (let* ((server-id (-> workspace lsp--workspace-client lsp--client-server-id symbol-name)) 8663 (pid (-> workspace lsp--workspace-cmd-proc lsp-process-id))) 8664 (get-buffer-create (format "*lsp-log: %s:%s*" server-id pid)))) 8665 8666 (defun lsp--erase-log-buffer (&optional all) 8667 "Delete contents of current lsp log buffer. 8668 When ALL is t, erase all log buffers of the running session." 8669 (interactive) 8670 (let* ((workspaces (lsp--session-workspaces (lsp-session))) 8671 (current-log-buffer (current-buffer))) 8672 (dolist (w workspaces) 8673 (let ((b (lsp--get-log-buffer-create w))) 8674 (when (or all (eq b current-log-buffer)) 8675 (with-current-buffer b 8676 (let ((inhibit-read-only t)) 8677 (erase-buffer)))))))) 8678 8679 (defun lsp--erase-session-log-buffers () 8680 "Erase log buffers of the running session." 8681 (interactive) 8682 (lsp--erase-log-buffer t)) 8683 8684 (defun lsp-log-io-next (arg) 8685 "Move to next log entry." 8686 (interactive "P") 8687 (ewoc-goto-next lsp--log-io-ewoc (or arg 1))) 8688 8689 (defun lsp-log-io-prev (arg) 8690 "Move to previous log entry." 8691 (interactive "P") 8692 (ewoc-goto-prev lsp--log-io-ewoc (or arg 1))) 8693 8694 8695 8696 (cl-defmethod lsp-process-id ((process process)) 8697 (process-id process)) 8698 8699 (cl-defmethod lsp-process-name ((process process)) (process-name process)) 8700 8701 (cl-defmethod lsp-process-status ((process process)) (process-status process)) 8702 8703 (cl-defmethod lsp-process-kill ((process process)) 8704 (when (process-live-p process) 8705 (kill-process process))) 8706 8707 (cl-defmethod lsp-process-send ((process process) message) 8708 (condition-case err 8709 (process-send-string process (lsp--make-message message)) 8710 (error (lsp--error "Sending to process failed with the following error: %s" 8711 (error-message-string err))))) 8712 8713 (cl-defmethod lsp-process-cleanup (process) 8714 ;; Kill standard error buffer only if the process exited normally. 8715 ;; Leave it intact otherwise for debugging purposes. 8716 (let ((buffer (-> process process-name get-buffer))) 8717 (when (and (eq (process-status process) 'exit) 8718 (zerop (process-exit-status process)) 8719 (buffer-live-p buffer)) 8720 (kill-buffer buffer)))) 8721 8722 8723 ;; native JSONRPC 8724 8725 (declare-function json-rpc "ext:json") 8726 (declare-function json-rpc-connection "ext:json") 8727 (declare-function json-rpc-send "ext:json") 8728 (declare-function json-rpc-shutdown "ext:json") 8729 (declare-function json-rpc-stderr "ext:json") 8730 (declare-function json-rpc-pid "ext:json") 8731 8732 (defvar lsp-json-rpc-thread nil) 8733 (defvar lsp-json-rpc-queue nil) 8734 (defvar lsp-json-rpc-done nil) 8735 (defvar lsp-json-rpc-mutex (make-mutex)) 8736 (defvar lsp-json-rpc-condition (make-condition-variable lsp-json-rpc-mutex)) 8737 8738 (defun lsp-json-rpc-process-queue () 8739 (while (not lsp-json-rpc-done) 8740 (while lsp-json-rpc-queue 8741 (-let (((proc . message) (pop lsp-json-rpc-queue))) 8742 (json-rpc-send 8743 proc message 8744 :null-object nil 8745 :false-object :json-false))) 8746 (with-mutex lsp-json-rpc-mutex 8747 (condition-wait lsp-json-rpc-condition)))) 8748 8749 (cl-defmethod lsp-process-id (process) (json-rpc-pid process)) 8750 8751 (cl-defmethod lsp-process-name (_process) "TBD") 8752 8753 (cl-defmethod lsp-process-kill (process) (json-rpc-shutdown process)) 8754 8755 (cl-defmethod lsp-process-send (proc message) 8756 (unless lsp-json-rpc-thread 8757 (with-current-buffer (get-buffer-create " *json-rpc*") 8758 (setq lsp-json-rpc-thread (make-thread #'lsp-json-rpc-process-queue "*json-rpc-queue*")))) 8759 8760 (with-mutex lsp-json-rpc-mutex 8761 (setq lsp-json-rpc-queue (append lsp-json-rpc-queue 8762 (list (cons proc message)))) 8763 (condition-notify lsp-json-rpc-condition))) 8764 8765 (cl-defmethod lsp-process-cleanup (_proc)) 8766 8767 (defun lsp-json-rpc-connection (workspace command) 8768 (let ((con (apply #'json-rpc-connection command)) 8769 (object-type (if lsp-use-plists 'plist 'hash-table))) 8770 (with-current-buffer (get-buffer-create " *json-rpc*") 8771 (make-thread 8772 (lambda () 8773 (json-rpc 8774 con 8775 (lambda (result err done) 8776 (run-with-timer 8777 0.0 8778 nil 8779 (lambda () 8780 (cond 8781 (result (lsp--parser-on-message result workspace)) 8782 (err (warn "Json parsing failed with the following error: %s" err)) 8783 (done (lsp--handle-process-exit workspace "")))))) 8784 :object-type object-type 8785 :null-object nil 8786 :false-object nil)) 8787 "*json-rpc-connection*")) 8788 (cons con con))) 8789 8790 (defun lsp-json-rpc-stderr () 8791 (interactive) 8792 (--when-let (pcase (lsp-workspaces) 8793 (`nil (user-error "There are no active servers in the current buffer")) 8794 (`(,workspace) workspace) 8795 (workspaces (lsp--completing-read "Select server: " 8796 workspaces 8797 'lsp--workspace-print nil t))) 8798 (let ((content (json-rpc-stderr (lsp--workspace-cmd-proc it))) 8799 (buffer (format "*stderr-%s*" (lsp--workspace-print it)) )) 8800 (with-current-buffer (get-buffer-create buffer) 8801 (with-help-window buffer 8802 (insert content)))))) 8803 8804 8805 (defun lsp--workspace-print (workspace) 8806 "Visual representation WORKSPACE." 8807 (let* ((proc (lsp--workspace-cmd-proc workspace)) 8808 (status (lsp--workspace-status workspace)) 8809 (server-id (-> workspace lsp--workspace-client lsp--client-server-id symbol-name)) 8810 (pid (lsp-process-id proc))) 8811 8812 (if (eq 'initialized status) 8813 (format "%s:%s" server-id pid) 8814 (format "%s:%s/%s" server-id pid status)))) 8815 8816 (defun lsp--map-tree-widget (m) 8817 "Build `tree-widget' from a hash-table or plist M." 8818 (when (lsp-structure-p m) 8819 (let (nodes) 8820 (lsp-map (lambda (k v) 8821 (push `(tree-widget 8822 :tag ,(if (lsp-structure-p v) 8823 (format "%s:" k) 8824 (format "%s: %s" k 8825 (propertize (format "%s" v) 8826 'face 8827 'font-lock-string-face))) 8828 :open t 8829 ,@(lsp--map-tree-widget v)) 8830 nodes)) 8831 m) 8832 nodes))) 8833 8834 (defun lsp-buffer-name (buffer-id) 8835 (if-let ((buffer-name (plist-get buffer-id :buffer-name))) 8836 (funcall buffer-name buffer-id) 8837 (buffer-name buffer-id))) 8838 8839 (defun lsp--render-workspace (workspace) 8840 "Tree node representation of WORKSPACE." 8841 `(tree-widget :tag ,(lsp--workspace-print workspace) 8842 :open t 8843 (tree-widget :tag ,(propertize "Buffers" 'face 'font-lock-function-name-face) 8844 :open t 8845 ,@(->> workspace 8846 (lsp--workspace-buffers) 8847 (--map `(tree-widget 8848 :tag ,(when (lsp-buffer-live-p it) 8849 (let ((buffer-name (lsp-buffer-name it))) 8850 (if (lsp-with-current-buffer it buffer-read-only) 8851 (propertize buffer-name 'face 'font-lock-constant-face) 8852 buffer-name))))))) 8853 (tree-widget :tag ,(propertize "Capabilities" 'face 'font-lock-function-name-face) 8854 ,@(-> workspace lsp--workspace-server-capabilities lsp--map-tree-widget)))) 8855 8856 (define-derived-mode lsp-browser-mode special-mode "LspBrowser" 8857 "Define mode for displaying lsp sessions." 8858 (setq-local display-buffer-base-action '(nil . ((inhibit-same-window . t))))) 8859 8860 (defun lsp-describe-session () 8861 "Describes current `lsp-session'." 8862 (interactive) 8863 (let ((session (lsp-session)) 8864 (buf (get-buffer-create "*lsp session*")) 8865 (root (lsp-workspace-root))) 8866 (with-current-buffer buf 8867 (lsp-browser-mode) 8868 (let ((inhibit-read-only t)) 8869 (erase-buffer) 8870 (--each (lsp-session-folders session) 8871 (widget-create 8872 `(tree-widget 8873 :tag ,(propertize it 'face 'font-lock-keyword-face) 8874 :open t 8875 ,@(->> session 8876 (lsp-session-folder->servers) 8877 (gethash it) 8878 (-map 'lsp--render-workspace))))))) 8879 (pop-to-buffer buf) 8880 (goto-char (point-min)) 8881 (cl-loop for tag = (widget-get (widget-get (widget-at) :node) :tag) 8882 until (or (and root (string= tag root)) (eobp)) 8883 do (goto-char (next-overlay-change (point)))))) 8884 8885 (defun lsp--session-workspaces (session) 8886 "Get all workspaces that are part of the SESSION." 8887 (-> session lsp-session-folder->servers hash-table-values -flatten -uniq)) 8888 8889 (defun lsp--find-multiroot-workspace (session client project-root) 8890 "Look for a multiroot connection in SESSION created from CLIENT for 8891 PROJECT-ROOT and BUFFER-MAJOR-MODE." 8892 (when (lsp--client-multi-root client) 8893 (-when-let (multi-root-workspace (->> session 8894 (lsp--session-workspaces) 8895 (--first (eq (-> it lsp--workspace-client lsp--client-server-id) 8896 (lsp--client-server-id client))))) 8897 (with-lsp-workspace multi-root-workspace 8898 (lsp-notify "workspace/didChangeWorkspaceFolders" 8899 (lsp-make-did-change-workspace-folders-params 8900 :event (lsp-make-workspace-folders-change-event 8901 :added (vector (lsp-make-workspace-folder 8902 :uri (lsp--path-to-uri project-root) 8903 :name (f-filename project-root))) 8904 :removed [])))) 8905 8906 (->> session (lsp-session-folder->servers) (gethash project-root) (cl-pushnew multi-root-workspace)) 8907 (->> session (lsp-session-server-id->folders) (gethash (lsp--client-server-id client)) (cl-pushnew project-root)) 8908 8909 (lsp--persist-session session) 8910 8911 (lsp--info "Opened folder %s in workspace %s" project-root (lsp--workspace-print multi-root-workspace)) 8912 (lsp--open-in-workspace multi-root-workspace) 8913 8914 multi-root-workspace))) 8915 8916 (defun lsp--ensure-lsp-servers (session clients project-root ignore-multi-folder) 8917 "Ensure that SESSION contain server CLIENTS created for PROJECT-ROOT. 8918 IGNORE-MULTI-FOLDER to ignore multi folder server." 8919 (-map (lambda (client) 8920 (or 8921 (lsp--find-workspace session client project-root) 8922 (unless ignore-multi-folder 8923 (lsp--find-multiroot-workspace session client project-root)) 8924 (lsp--start-connection session client project-root))) 8925 clients)) 8926 8927 (defun lsp--spinner-stop () 8928 "Stop the spinner in case all of the workspaces are started." 8929 (when (--all? (eq (lsp--workspace-status it) 'initialized) 8930 lsp--buffer-workspaces) 8931 (spinner-stop))) 8932 8933 (defun lsp--open-in-workspace (workspace) 8934 "Open in existing WORKSPACE." 8935 (if (eq 'initialized (lsp--workspace-status workspace)) 8936 ;; when workspace is initialized just call document did open. 8937 (progn 8938 (with-lsp-workspace workspace 8939 (when-let ((before-document-open-fn (-> workspace 8940 lsp--workspace-client 8941 lsp--client-before-file-open-fn))) 8942 (funcall before-document-open-fn workspace)) 8943 (lsp--text-document-did-open)) 8944 (lsp--spinner-stop)) 8945 ;; when it is not initialized 8946 (lsp--spinner-start) 8947 (cl-pushnew (lsp-current-buffer) (lsp--workspace-buffers workspace)))) 8948 8949 (defun lsp--find-workspace (session client project-root) 8950 "Find server connection created with CLIENT in SESSION for PROJECT-ROOT." 8951 (when-let ((workspace (->> session 8952 (lsp-session-folder->servers) 8953 (gethash project-root) 8954 (--first (eql (-> it lsp--workspace-client lsp--client-server-id) 8955 (lsp--client-server-id client)))))) 8956 (lsp--open-in-workspace workspace) 8957 workspace)) 8958 8959 (defun lsp--read-char (prompt &optional options) 8960 "Wrapper for `read-char-from-minibuffer' if Emacs +27. 8961 Fallback to `read-key' otherwise. 8962 PROMPT is the message and OPTIONS the available options." 8963 (if (fboundp 'read-char-from-minibuffer) 8964 (read-char-from-minibuffer prompt options) 8965 (read-key prompt))) 8966 8967 (defun lsp--find-root-interactively (session) 8968 "Find project interactively. 8969 Returns nil if the project should not be added to the current SESSION." 8970 (condition-case nil 8971 (let* ((project-root-suggestion (or (lsp--suggest-project-root) default-directory)) 8972 (action (lsp--read-char 8973 (format 8974 "%s is not part of any project. 8975 8976 %s ==> Import project root %s 8977 %s ==> Import project by selecting root directory interactively 8978 %s ==> Import project at current directory %s 8979 %s ==> Do not ask again for the current project by adding %s to lsp-session-folders-blocklist 8980 %s ==> Do not ask again for the current project by selecting ignore path interactively 8981 %s ==> Do nothing: ask again when opening other files from the current project 8982 8983 Select action: " 8984 (propertize (buffer-name) 'face 'bold) 8985 (propertize "i" 'face 'success) 8986 (propertize project-root-suggestion 'face 'bold) 8987 (propertize "I" 'face 'success) 8988 (propertize "." 'face 'success) 8989 (propertize default-directory 'face 'bold) 8990 (propertize "d" 'face 'warning) 8991 (propertize project-root-suggestion 'face 'bold) 8992 (propertize "D" 'face 'warning) 8993 (propertize "n" 'face 'warning)) 8994 '(?i ?\r ?I ?. ?d ?D ?n)))) 8995 (cl-case action 8996 (?i project-root-suggestion) 8997 (?\r project-root-suggestion) 8998 (?I (read-directory-name "Select workspace folder to add: " 8999 (or project-root-suggestion default-directory) 9000 nil 9001 t)) 9002 (?. default-directory) 9003 (?d (push project-root-suggestion (lsp-session-folders-blocklist session)) 9004 (lsp--persist-session session) 9005 nil) 9006 (?D (push (read-directory-name "Select folder to blocklist: " 9007 (or project-root-suggestion default-directory) 9008 nil 9009 t) 9010 (lsp-session-folders-blocklist session)) 9011 (lsp--persist-session session) 9012 nil) 9013 (t nil))) 9014 (quit))) 9015 9016 (declare-function tramp-file-name-host "ext:tramp" (file) t) 9017 (declare-function tramp-dissect-file-name "ext:tramp" (file &optional nodefault)) 9018 9019 (defun lsp--files-same-host (f1 f2) 9020 "Predicate on whether or not two files are on the same host." 9021 (or (not (or (file-remote-p f1) (file-remote-p f2))) 9022 (and (file-remote-p f1) 9023 (file-remote-p f2) 9024 (progn (require 'tramp) 9025 (equal (tramp-file-name-host (tramp-dissect-file-name f1)) 9026 (tramp-file-name-host (tramp-dissect-file-name f2))))))) 9027 9028 (defun lsp-find-session-folder (session file-name) 9029 "Look in the current SESSION for folder containing FILE-NAME." 9030 (let ((file-name-canonical (lsp-f-canonical file-name))) 9031 (->> session 9032 (lsp-session-folders) 9033 (--filter (and (lsp--files-same-host it file-name-canonical) 9034 (or (lsp-f-same? it file-name-canonical) 9035 (and (f-dir? it) 9036 (lsp-f-ancestor-of? it file-name-canonical))))) 9037 (--max-by (> (length it) 9038 (length other)))))) 9039 9040 (defun lsp-find-workspace (server-id &optional file-name) 9041 "Find workspace for SERVER-ID for FILE-NAME." 9042 (-when-let* ((session (lsp-session)) 9043 (folder->servers (lsp-session-folder->servers session)) 9044 (workspaces (if file-name 9045 (gethash (lsp-find-session-folder session file-name) folder->servers) 9046 (lsp--session-workspaces session)))) 9047 9048 (--first (eq (lsp--client-server-id (lsp--workspace-client it)) server-id) workspaces))) 9049 9050 (defun lsp--calculate-root (session file-name) 9051 "Calculate project root for FILE-NAME in SESSION." 9052 (and 9053 (->> session 9054 (lsp-session-folders-blocklist) 9055 (--first (and (lsp--files-same-host it file-name) 9056 (lsp-f-ancestor-of? it file-name) 9057 (prog1 t 9058 (lsp--info "File %s is in blocklisted directory %s" file-name it)))) 9059 not) 9060 (or 9061 (when lsp-auto-guess-root 9062 (lsp--suggest-project-root)) 9063 (unless lsp-guess-root-without-session 9064 (lsp-find-session-folder session file-name)) 9065 (unless lsp-auto-guess-root 9066 (when-let ((root-folder (lsp--find-root-interactively session))) 9067 (if (or (not (f-equal? root-folder (expand-file-name "~/"))) 9068 (yes-or-no-p 9069 (concat 9070 (propertize "[WARNING] " 'face 'warning) 9071 "You are trying to import your home folder as project root. This may cause performance issue because some language servers (python, lua, etc) will try to scan all files under project root. To avoid that you may: 9072 9073 1. Use `I' option from the interactive project import to select subfolder(e. g. `~/foo/bar' instead of `~/'). 9074 2. If your file is under `~/' then create a subfolder and move that file in this folder. 9075 9076 Type `No' to go back to project selection. 9077 Type `Yes' to confirm `HOME' as project root. 9078 Type `C-g' to cancel project import process and stop `lsp'"))) 9079 root-folder 9080 (lsp--calculate-root session file-name))))))) 9081 9082 (defun lsp--try-open-in-library-workspace () 9083 "Try opening current file as library file in any of the active workspace. 9084 The library folders are defined by each client for each of the active workspace." 9085 (when-let ((workspace (->> (lsp-session) 9086 (lsp--session-workspaces) 9087 ;; Sort the last active workspaces first as they are more likely to be 9088 ;; the correct ones, especially when jumping to a definition. 9089 (-sort (lambda (a _b) 9090 (-contains? lsp--last-active-workspaces a))) 9091 (--first 9092 (and (-> it lsp--workspace-client lsp--supports-buffer?) 9093 (when-let ((library-folders-fn 9094 (-> it lsp--workspace-client lsp--client-library-folders-fn))) 9095 (-first (lambda (library-folder) 9096 (lsp-f-ancestor-of? library-folder (buffer-file-name))) 9097 (funcall library-folders-fn it)))))))) 9098 (lsp--open-in-workspace workspace) 9099 (view-mode t) 9100 (lsp--info "Opening read-only library file %s." (buffer-file-name)) 9101 (list workspace))) 9102 9103 (defun lsp--persist-session (session) 9104 "Persist SESSION to `lsp-session-file'." 9105 (lsp--persist lsp-session-file (make-lsp-session 9106 :folders (lsp-session-folders session) 9107 :folders-blocklist (lsp-session-folders-blocklist session) 9108 :server-id->folders (lsp-session-server-id->folders session)))) 9109 9110 (defun lsp--try-project-root-workspaces (ask-for-client ignore-multi-folder) 9111 "Try create opening file as a project file. 9112 When IGNORE-MULTI-FOLDER is t the lsp mode will start new 9113 language server even if there is language server which can handle 9114 current language. When IGNORE-MULTI-FOLDER is nil current file 9115 will be opened in multi folder language server if there is 9116 such." 9117 (-let ((session (lsp-session))) 9118 (-if-let (clients (if ask-for-client 9119 (list (lsp--completing-read "Select server to start: " 9120 (ht-values lsp-clients) 9121 (-compose 'symbol-name 'lsp--client-server-id) nil t)) 9122 (lsp--find-clients))) 9123 (-if-let (project-root (-some-> session 9124 (lsp--calculate-root (buffer-file-name)) 9125 (lsp-f-canonical))) 9126 (progn 9127 ;; update project roots if needed and persist the lsp session 9128 (unless (-contains? (lsp-session-folders session) project-root) 9129 (cl-pushnew project-root (lsp-session-folders session)) 9130 (lsp--persist-session session)) 9131 (lsp--ensure-lsp-servers session clients project-root ignore-multi-folder)) 9132 (lsp--warn "%s not in project or it is blocklisted." (buffer-name)) 9133 nil) 9134 (lsp--warn "No LSP server for %s(check *lsp-log*)." major-mode) 9135 nil))) 9136 9137 (defun lsp-shutdown-workspace () 9138 "Shutdown language server." 9139 (interactive) 9140 (--when-let (pcase (lsp-workspaces) 9141 (`nil (user-error "There are no active servers in the current buffer")) 9142 (`(,workspace) (when (y-or-n-p (format "Are you sure you want to stop the server %s?" 9143 (lsp--workspace-print workspace))) 9144 workspace)) 9145 (workspaces (lsp--completing-read "Select server: " 9146 workspaces 9147 'lsp--workspace-print nil t))) 9148 (lsp-workspace-shutdown it))) 9149 9150 (make-obsolete 'lsp-shutdown-workspace 'lsp-workspace-shutdown "lsp-mode 6.1") 9151 9152 (defcustom lsp-auto-select-workspace t 9153 "Shutdown or restart a single workspace. 9154 If set and the current buffer has only a single workspace 9155 associated with it, `lsp-shutdown-workspace' and 9156 `lsp-restart-workspace' will act on it without asking." 9157 :type 'boolean 9158 :group 'lsp-mode) 9159 9160 (defun lsp--read-workspace () 9161 "Ask the user to select a workspace. 9162 Errors if there are none." 9163 (pcase (lsp-workspaces) 9164 (`nil (error "No workspaces associated with the current buffer")) 9165 ((and `(,workspace) (guard lsp-auto-select-workspace)) workspace) 9166 (workspaces (lsp--completing-read "Select workspace: " workspaces 9167 #'lsp--workspace-print nil t)))) 9168 9169 (defun lsp-workspace-shutdown (workspace) 9170 "Shut the workspace WORKSPACE and the language server associated with it" 9171 (interactive (list (lsp--read-workspace))) 9172 (lsp--warn "Stopping %s" (lsp--workspace-print workspace)) 9173 (with-lsp-workspace workspace (lsp--shutdown-workspace))) 9174 9175 (defun lsp-disconnect () 9176 "Disconnect the buffer from the language server." 9177 (interactive) 9178 (lsp--text-document-did-close t) 9179 (lsp-managed-mode -1) 9180 (lsp-mode -1) 9181 (setq lsp--buffer-workspaces nil) 9182 (lsp--info "Disconnected")) 9183 9184 (defun lsp-restart-workspace () 9185 (interactive) 9186 (--when-let (pcase (lsp-workspaces) 9187 (`nil (user-error "There are no active servers in the current buffer")) 9188 (`(,workspace) workspace) 9189 (workspaces (lsp--completing-read "Select server: " 9190 workspaces 9191 'lsp--workspace-print nil t))) 9192 (lsp-workspace-restart it))) 9193 9194 (make-obsolete 'lsp-restart-workspace 'lsp-workspace-restart "lsp-mode 6.1") 9195 9196 (defun lsp-workspace-restart (workspace) 9197 "Restart the workspace WORKSPACE and the language server associated with it" 9198 (interactive (list (lsp--read-workspace))) 9199 (lsp--warn "Restarting %s" (lsp--workspace-print workspace)) 9200 (with-lsp-workspace workspace (lsp--shutdown-workspace t))) 9201 9202 ;;;###autoload 9203 (defun lsp (&optional arg) 9204 "Entry point for the server startup. 9205 When ARG is t the lsp mode will start new language server even if 9206 there is language server which can handle current language. When 9207 ARG is nil current file will be opened in multi folder language 9208 server if there is such. When `lsp' is called with prefix 9209 argument ask the user to select which language server to start." 9210 (interactive "P") 9211 9212 (lsp--require-packages) 9213 9214 (when (buffer-file-name) 9215 (let (clients 9216 (matching-clients (lsp--filter-clients 9217 (-andfn #'lsp--supports-buffer? 9218 #'lsp--server-binary-present?)))) 9219 (cond 9220 (matching-clients 9221 (when (setq lsp--buffer-workspaces 9222 (or (and 9223 ;; Don't open as library file if file is part of a project. 9224 (not (lsp-find-session-folder (lsp-session) (buffer-file-name))) 9225 (lsp--try-open-in-library-workspace)) 9226 (lsp--try-project-root-workspaces (equal arg '(4)) 9227 (and arg (not (equal arg 1)))))) 9228 (lsp-mode 1) 9229 (when lsp-auto-configure (lsp--auto-configure)) 9230 (setq lsp-buffer-uri (lsp--buffer-uri)) 9231 (lsp--info "Connected to %s." 9232 (apply 'concat (--map (format "[%s %s]" 9233 (lsp--workspace-print it) 9234 (lsp--workspace-root it)) 9235 lsp--buffer-workspaces))))) 9236 ;; look for servers which are currently being downloaded. 9237 ((setq clients (lsp--filter-clients (-andfn #'lsp--supports-buffer? 9238 #'lsp--client-download-in-progress?))) 9239 (lsp--info "There are language server(%s) installation in progress. 9240 The server(s) will be started in the buffer when it has finished." 9241 (-map #'lsp--client-server-id clients)) 9242 (seq-do (lambda (client) 9243 (cl-pushnew (current-buffer) (lsp--client-buffers client))) 9244 clients)) 9245 ;; look for servers to install 9246 ((setq clients (lsp--filter-clients 9247 (-andfn #'lsp--supports-buffer? 9248 (-const lsp-enable-suggest-server-download) 9249 #'lsp--client-download-server-fn 9250 (-not #'lsp--client-download-in-progress?)))) 9251 (let ((client (lsp--completing-read 9252 (concat "Unable to find installed server supporting this file. " 9253 "The following servers could be installed automatically: ") 9254 clients 9255 (-compose #'symbol-name #'lsp--client-server-id) 9256 nil 9257 t))) 9258 (cl-pushnew (current-buffer) (lsp--client-buffers client)) 9259 (lsp--install-server-internal client))) 9260 ;; ignore other warnings 9261 ((not lsp-warn-no-matched-clients) 9262 nil) 9263 ;; automatic installation disabled 9264 ((setq clients (unless matching-clients 9265 (lsp--filter-clients (-andfn #'lsp--supports-buffer? 9266 #'lsp--client-download-server-fn 9267 (-not (-const lsp-enable-suggest-server-download)) 9268 (-not #'lsp--server-binary-present?))))) 9269 (lsp--warn "The following servers support current file but automatic download is disabled: %s 9270 \(If you have already installed the server check *lsp-log*)." 9271 (mapconcat (lambda (client) 9272 (symbol-name (lsp--client-server-id client))) 9273 clients 9274 " "))) 9275 ;; no clients present 9276 ((setq clients (unless matching-clients 9277 (lsp--filter-clients (-andfn #'lsp--supports-buffer? 9278 (-not #'lsp--server-binary-present?))))) 9279 (lsp--warn "The following servers support current file but do not have automatic installation: %s 9280 You may find the installation instructions at https://emacs-lsp.github.io/lsp-mode/page/languages. 9281 \(If you have already installed the server check *lsp-log*)." 9282 (mapconcat (lambda (client) 9283 (symbol-name (lsp--client-server-id client))) 9284 clients 9285 " "))) 9286 ;; no matches 9287 ((-> #'lsp--supports-buffer? lsp--filter-clients not) 9288 (lsp--error "There are no language servers supporting current mode `%s' registered with `lsp-mode'. 9289 This issue might be caused by: 9290 1. The language you are trying to use does not have built-in support in `lsp-mode'. You must install the required support manually. Examples of this are `lsp-java' or `lsp-metals'. 9291 2. The language server that you expect to run is not configured to run for major mode `%s'. You may check that by checking the `:major-modes' that are passed to `lsp-register-client'. 9292 3. `lsp-mode' doesn't have any integration for the language behind `%s'. Refer to https://emacs-lsp.github.io/lsp-mode/page/languages and https://langserver.org/ . 9293 4. You are over `tramp'. In this case follow https://emacs-lsp.github.io/lsp-mode/page/remote/. 9294 5. You have disabled the `lsp-mode' clients for that file. (Check `lsp-enabled-clients' and `lsp-disabled-clients'). 9295 You can customize `lsp-warn-no-matched-clients' to disable this message." 9296 major-mode major-mode major-mode)))))) 9297 9298 (defun lsp--buffer-visible-p () 9299 "Return non nil if current buffer is visible." 9300 (or (buffer-modified-p) (get-buffer-window nil t))) 9301 9302 (defun lsp--init-if-visible () 9303 "Run `lsp' for the current buffer if the buffer is visible. 9304 Returns non nil if `lsp' was run for the buffer." 9305 (when (lsp--buffer-visible-p) 9306 (remove-hook 'window-configuration-change-hook #'lsp--init-if-visible t) 9307 (lsp) 9308 t)) 9309 9310 ;;;###autoload 9311 (defun lsp-deferred () 9312 "Entry point that defers server startup until buffer is visible. 9313 `lsp-deferred' will wait until the buffer is visible before invoking `lsp'. 9314 This avoids overloading the server with many files when starting Emacs." 9315 ;; Workspace may not be initialized yet. Use a buffer local variable to 9316 ;; remember that we deferred loading of this buffer. 9317 (setq lsp--buffer-deferred t) 9318 (let ((buffer (current-buffer))) 9319 ;; Avoid false positives as desktop-mode restores buffers by deferring 9320 ;; visibility check until the stack clears. 9321 (run-with-idle-timer 0 nil (lambda () 9322 (when (buffer-live-p buffer) 9323 (with-current-buffer buffer 9324 (unless (lsp--init-if-visible) 9325 (add-hook 'window-configuration-change-hook #'lsp--init-if-visible nil t)))))))) 9326 9327 9328 9329 (defvar lsp-file-truename-cache (ht)) 9330 9331 (defmacro lsp-with-cached-filetrue-name (&rest body) 9332 "Executes BODY caching the `file-truename' calls." 9333 `(let ((old-fn (symbol-function 'file-truename))) 9334 (unwind-protect 9335 (progn 9336 (fset 'file-truename 9337 (lambda (file-name &optional counter prev-dirs) 9338 (or (gethash file-name lsp-file-truename-cache) 9339 (puthash file-name (apply old-fn (list file-name counter prev-dirs)) 9340 lsp-file-truename-cache)))) 9341 ,@body) 9342 (fset 'file-truename old-fn)))) 9343 9344 9345 (defun lsp-virtual-buffer-call (key &rest args) 9346 (when lsp--virtual-buffer 9347 (when-let ((fn (plist-get lsp--virtual-buffer key))) 9348 (apply fn args)))) 9349 9350 (defun lsp-translate-column (column) 9351 "Translate COLUMN taking into account virtual buffers." 9352 (or (lsp-virtual-buffer-call :real->virtual-char column) 9353 column)) 9354 9355 (defun lsp-translate-line (line) 9356 "Translate LINE taking into account virtual buffers." 9357 (or (lsp-virtual-buffer-call :real->virtual-line line) 9358 line)) 9359 9360 9361 ;; lsp internal validation. 9362 9363 (defmacro lsp--doctor (&rest checks) 9364 `(-let [buf (current-buffer)] 9365 (with-current-buffer (get-buffer-create "*lsp-performance*") 9366 (with-help-window (current-buffer) 9367 ,@(-map (-lambda ((msg form)) 9368 `(insert (format "%s: %s\n" ,msg 9369 (let ((res (with-current-buffer buf 9370 ,form))) 9371 (cond 9372 ((eq res :optional) (propertize "OPTIONAL" 'face 'warning)) 9373 (res (propertize "OK" 'face 'success)) 9374 (t (propertize "ERROR" 'face 'error))))))) 9375 (-partition 2 checks)))))) 9376 9377 (define-obsolete-function-alias 'lsp-diagnose 9378 'lsp-doctor "lsp-mode 8.0.0") 9379 9380 (defun lsp-doctor () 9381 "Validate performance settings." 9382 (interactive) 9383 (lsp--doctor 9384 "Checking for Native JSON support" (functionp 'json-serialize) 9385 "Check emacs supports `read-process-output-max'" (boundp 'read-process-output-max) 9386 "Check `read-process-output-max' default has been changed from 4k" 9387 (and (boundp 'read-process-output-max) 9388 (> read-process-output-max 4096)) 9389 "Byte compiled against Native JSON (recompile lsp-mode if failing when Native JSON available)" 9390 (condition-case _err 9391 (progn (lsp--make-message (list "a" "b")) 9392 nil) 9393 (error t)) 9394 "`gc-cons-threshold' increased?" (> gc-cons-threshold 800000) 9395 "Using `plist' for deserialized objects? (refer to https://emacs-lsp.github.io/lsp-mode/page/performance/#use-plists-for-deserialization)" (or lsp-use-plists :optional) 9396 "Using emacs 28+ with native compilation?" 9397 (or (and (fboundp 'native-comp-available-p) 9398 (native-comp-available-p)) 9399 :optional))) 9400 9401 (declare-function package-version-join "ext:package") 9402 (declare-function package-desc-version "ext:package") 9403 (declare-function package--alist "ext:package") 9404 9405 (defun lsp-version () 9406 "Return string describing current version of `lsp-mode'." 9407 (interactive) 9408 (unless (featurep 'package) 9409 (require 'package)) 9410 (let ((ver (format "lsp-mode %s, Emacs %s, %s" 9411 (package-version-join 9412 (package-desc-version 9413 (car (alist-get 'lsp-mode (package--alist))))) 9414 emacs-version 9415 system-type))) 9416 (if (called-interactively-p 'interactive) 9417 (lsp--info "%s" ver) 9418 ver))) 9419 9420 9421 9422 ;; org-mode/virtual-buffer 9423 9424 (declare-function org-babel-get-src-block-info "ext:ob-core") 9425 (declare-function org-do-remove-indentation "ext:org-macs") 9426 (declare-function org-src-get-lang-mode "ext:org-src") 9427 (declare-function org-element-context "ext:org-element") 9428 9429 (defun lsp--virtual-buffer-update-position () 9430 (-if-let (virtual-buffer (-first (-lambda ((&plist :in-range)) 9431 (funcall in-range)) 9432 lsp--virtual-buffer-connections)) 9433 (unless (equal virtual-buffer lsp--virtual-buffer) 9434 (lsp-org)) 9435 (when lsp-managed-mode 9436 (lsp-managed-mode -1) 9437 (lsp-mode -1) 9438 (setq lsp--buffer-workspaces nil) 9439 (setq lsp--virtual-buffer nil) 9440 (setq lsp-buffer-uri nil) 9441 9442 ;; force refresh of diagnostics 9443 (run-hooks 'lsp-after-diagnostics-hook)))) 9444 9445 (defun lsp-virtual-buffer-on-change (start end length) 9446 "Adjust on change event to be executed against the proper language server." 9447 (let ((max-point (max end 9448 (or (plist-get lsp--before-change-vals :end) 0) 9449 (+ start length)))) 9450 (when-let ((virtual-buffer (-first (lambda (vb) 9451 (let ((lsp--virtual-buffer vb)) 9452 (and (lsp-virtual-buffer-call :in-range start) 9453 (lsp-virtual-buffer-call :in-range max-point)))) 9454 lsp--virtual-buffer-connections))) 9455 (lsp-with-current-buffer virtual-buffer 9456 (lsp-on-change start end length 9457 (lambda (&rest _) 9458 (list :range (lsp--range (list :character 0 :line 0) 9459 lsp--virtual-buffer-point-max) 9460 :text (lsp--buffer-content)))))))) 9461 9462 (defun lsp-virtual-buffer-before-change (start _end) 9463 (when-let ((virtual-buffer (-first (lambda (vb) 9464 (lsp-with-current-buffer vb 9465 (lsp-virtual-buffer-call :in-range start))) 9466 lsp--virtual-buffer-connections))) 9467 (lsp-with-current-buffer virtual-buffer 9468 (setq lsp--virtual-buffer-point-max 9469 (lsp--point-to-position (lsp-virtual-buffer-call :last-point)))))) 9470 9471 (defun lsp-patch-on-change-event () 9472 (remove-hook 'after-change-functions #'lsp-on-change t) 9473 (add-hook 'after-change-functions #'lsp-virtual-buffer-on-change nil t) 9474 (add-hook 'before-change-functions #'lsp-virtual-buffer-before-change nil t)) 9475 9476 (defun lsp-kill-virtual-buffers () 9477 (mapc #'lsp-virtual-buffer-disconnect lsp--virtual-buffer-connections)) 9478 9479 (defun lsp--move-point-in-indentation (point indentation) 9480 (save-excursion 9481 (goto-char point) 9482 (if (<= point (+ (line-beginning-position) indentation)) 9483 (line-beginning-position) 9484 point))) 9485 9486 (declare-function flycheck-checker-supports-major-mode-p "ext:flycheck") 9487 (declare-function flycheck-add-mode "ext:flycheck") 9488 (declare-function lsp-diagnostics-lsp-checker-if-needed "lsp-diagnostics") 9489 9490 (defalias 'lsp-client-download-server-fn 'lsp--client-download-server-fn) 9491 9492 (defun lsp-flycheck-add-mode (mode) 9493 "Register flycheck support for MODE." 9494 (lsp-diagnostics-lsp-checker-if-needed) 9495 (unless (flycheck-checker-supports-major-mode-p 'lsp mode) 9496 (flycheck-add-mode 'lsp mode))) 9497 9498 (defun lsp-progress-spinner-type () 9499 "Retrieve the spinner type value, if value is not a symbol of `spinner-types 9500 defaults to `progress-bar." 9501 (or (car (assoc lsp-progress-spinner-type spinner-types)) 'progress-bar)) 9502 9503 (defun lsp-org () 9504 (interactive) 9505 (-if-let ((virtual-buffer &as &plist :workspaces) (-first (-lambda ((&plist :in-range)) 9506 (funcall in-range)) 9507 lsp--virtual-buffer-connections)) 9508 (unless (equal lsp--virtual-buffer virtual-buffer) 9509 (setq lsp--buffer-workspaces workspaces) 9510 (setq lsp--virtual-buffer virtual-buffer) 9511 (setq lsp-buffer-uri nil) 9512 (lsp-mode 1) 9513 (lsp-managed-mode 1) 9514 (lsp-patch-on-change-event)) 9515 9516 (save-excursion 9517 (-let* (virtual-buffer 9518 (wcb (lambda (f) 9519 (with-current-buffer (plist-get virtual-buffer :buffer) 9520 (-let* (((&plist :major-mode :buffer-file-name 9521 :goto-buffer :workspaces) virtual-buffer) 9522 (lsp--virtual-buffer virtual-buffer) 9523 (lsp--buffer-workspaces workspaces)) 9524 (save-excursion 9525 (funcall goto-buffer) 9526 (funcall f)))))) 9527 ((&plist :begin :end :post-blank :language) (cl-second (org-element-context))) 9528 ((&alist :tangle file-name) (cl-third (org-babel-get-src-block-info 'light))) 9529 9530 (file-name (if file-name 9531 (f-expand file-name) 9532 (user-error "You should specify file name in the src block header."))) 9533 (begin-marker (progn 9534 (goto-char begin) 9535 (forward-line) 9536 (set-marker (make-marker) (point)))) 9537 (end-marker (progn 9538 (goto-char end) 9539 (forward-line (1- (- post-blank))) 9540 (set-marker (make-marker) (1+ (point))))) 9541 (buf (current-buffer)) 9542 (src-block (buffer-substring-no-properties begin-marker 9543 (1- end-marker))) 9544 (indentation (with-temp-buffer 9545 (insert src-block) 9546 9547 (goto-char (point-min)) 9548 (let ((indentation (current-indentation))) 9549 (plist-put lsp--virtual-buffer :indentation indentation) 9550 (org-do-remove-indentation) 9551 (goto-char (point-min)) 9552 (- indentation (current-indentation)))))) 9553 (add-hook 'post-command-hook #'lsp--virtual-buffer-update-position nil t) 9554 9555 (when (fboundp 'flycheck-add-mode) 9556 (lsp-flycheck-add-mode 'org-mode)) 9557 9558 (setq lsp--virtual-buffer 9559 (list 9560 :in-range (lambda (&optional point) 9561 (<= begin-marker (or point (point)) (1- end-marker))) 9562 :goto-buffer (lambda () (goto-char begin-marker)) 9563 :buffer-string 9564 (lambda () 9565 (let ((src-block (buffer-substring-no-properties 9566 begin-marker 9567 (1- end-marker)))) 9568 (with-temp-buffer 9569 (insert src-block) 9570 9571 (goto-char (point-min)) 9572 (while (not (eobp)) 9573 (delete-region (point) (if (> (+ (point) indentation) (line-end-position)) 9574 (line-end-position) 9575 (+ (point) indentation))) 9576 (forward-line)) 9577 (buffer-substring-no-properties (point-min) 9578 (point-max))))) 9579 :buffer buf 9580 :begin begin-marker 9581 :end end-marker 9582 :indentation indentation 9583 :last-point (lambda () (1- end-marker)) 9584 :cur-position (lambda () 9585 (lsp-save-restriction-and-excursion 9586 (list :line (- (lsp--cur-line) 9587 (lsp--cur-line begin-marker)) 9588 :character (let ((character (- (point) 9589 (line-beginning-position) 9590 indentation))) 9591 (if (< character 0) 9592 0 9593 character))))) 9594 :line/character->point (-lambda (line character) 9595 (-let [inhibit-field-text-motion t] 9596 (+ indentation 9597 (lsp-save-restriction-and-excursion 9598 (goto-char begin-marker) 9599 (forward-line line) 9600 (-let [line-end (line-end-position)] 9601 (if (> character (- line-end (point))) 9602 line-end 9603 (forward-char character) 9604 (point))))))) 9605 :major-mode (org-src-get-lang-mode language) 9606 :buffer-file-name file-name 9607 :buffer-uri (lsp--path-to-uri file-name) 9608 :with-current-buffer wcb 9609 :buffer-live? (lambda (_) (buffer-live-p buf)) 9610 :buffer-name (lambda (_) 9611 (propertize (format "%s(%s:%s)%s" 9612 (buffer-name buf) 9613 begin-marker 9614 end-marker 9615 language) 9616 'face 'italic)) 9617 :real->virtual-line (lambda (line) 9618 (+ line (line-number-at-pos begin-marker) -1)) 9619 :real->virtual-char (lambda (char) (+ char indentation)) 9620 :cleanup (lambda () 9621 (set-marker begin-marker nil) 9622 (set-marker end-marker nil)))) 9623 (setf virtual-buffer lsp--virtual-buffer) 9624 (puthash file-name virtual-buffer lsp--virtual-buffer-mappings) 9625 (push virtual-buffer lsp--virtual-buffer-connections) 9626 9627 ;; TODO: tangle only connected sections 9628 (add-hook 'after-save-hook 'org-babel-tangle nil t) 9629 (add-hook 'lsp-after-open-hook #'lsp-patch-on-change-event nil t) 9630 (add-hook 'kill-buffer-hook #'lsp-kill-virtual-buffers nil t) 9631 9632 (setq lsp--buffer-workspaces 9633 (lsp-with-current-buffer virtual-buffer 9634 (lsp) 9635 (plist-put virtual-buffer :workspaces (lsp-workspaces)) 9636 (lsp-workspaces))))))) 9637 9638 (defun lsp-virtual-buffer-disconnect (virtual-buffer) 9639 (interactive (list (or 9640 lsp--virtual-buffer 9641 (when lsp--virtual-buffer-connections 9642 (lsp--completing-read "Select virtual buffer to disconnect: " 9643 lsp--virtual-buffer-connections 9644 (-lambda ((&plist :buffer-file-name)) 9645 buffer-file-name)))))) 9646 (-if-let ((&plist :buffer-file-name file-name :cleanup) virtual-buffer) 9647 (progn 9648 (lsp-with-current-buffer virtual-buffer 9649 (lsp--text-document-did-close)) 9650 (setq lsp--virtual-buffer-connections (-remove-item virtual-buffer lsp--virtual-buffer-connections)) 9651 (when (eq virtual-buffer lsp--virtual-buffer) 9652 (setf lsp--virtual-buffer nil)) 9653 (when cleanup (funcall cleanup)) 9654 (remhash file-name lsp--virtual-buffer-mappings) 9655 9656 (lsp--virtual-buffer-update-position) 9657 (lsp--info "Disconnected from buffer %s" file-name)) 9658 (lsp--error "Nothing to disconnect from?"))) 9659 9660 9661 ;; inlay hints 9662 9663 (defface lsp-inlay-hint-face 9664 '((t :inherit font-lock-comment-face)) 9665 "The face to use for the JavaScript inlays." 9666 :group 'lsp-mode 9667 :package-version '(lsp-mode . "9.0.0")) 9668 9669 (defface lsp-inlay-hint-type-face 9670 '((t :inherit lsp-inlay-hint-face)) 9671 "Face for inlay type hints (e.g. inferred variable types)." 9672 :group 'lsp-mode 9673 :package-version '(lsp-mode . "9.0.0")) 9674 9675 (defcustom lsp-inlay-hint-type-format "%s" 9676 "Format string for variable inlays (part of the inlay face)." 9677 :type '(string :tag "String") 9678 :group 'lsp-mode 9679 :package-version '(lsp-mode . "9.0.0")) 9680 9681 (defface lsp-inlay-hint-parameter-face 9682 '((t :inherit lsp-inlay-hint-face)) 9683 "Face for inlay parameter hints (e.g. function parameter names at 9684 call-site)." 9685 :group 'lsp-mode 9686 :package-version '(lsp-mode . "9.0.0")) 9687 9688 (defcustom lsp-inlay-hint-param-format "%s" 9689 "Format string for parameter inlays (part of the inlay face)." 9690 :type '(string :tag "String") 9691 :group 'lsp-mode 9692 :package-version '(lsp-mode . "9.0.0")) 9693 9694 (defcustom lsp-update-inlay-hints-on-scroll t 9695 "If non-nil update inlay hints immediately when scrolling or 9696 modifying window sizes." 9697 :type 'boolean 9698 :package-version '(lsp-mode . "9.0.0")) 9699 9700 (defun lsp--format-inlay (text kind) 9701 (cond 9702 ((eql kind lsp/inlay-hint-kind-type-hint) (format lsp-inlay-hint-type-format text)) 9703 ((eql kind lsp/inlay-hint-kind-parameter-hint) (format lsp-inlay-hint-param-format text)) 9704 (t text))) 9705 9706 (defun lsp--face-for-inlay (kind) 9707 (cond 9708 ((eql kind lsp/inlay-hint-kind-type-hint) 'lsp-inlay-hint-type-face) 9709 ((eql kind lsp/inlay-hint-kind-parameter-hint) 'lsp-inlay-hint-parameter-face) 9710 (t 'lsp-inlay-hint-face))) 9711 9712 (defun lsp--update-inlay-hints-scroll-function (window start) 9713 (lsp-update-inlay-hints start (window-end window t))) 9714 9715 (defun lsp--update-inlay-hints () 9716 (lsp-update-inlay-hints (window-start) (window-end nil t))) 9717 9718 (defun lsp--label-from-inlay-hints-response (label) 9719 "Returns a string label built from an array of 9720 InlayHintLabelParts or the argument itself if it's already a 9721 string." 9722 (cl-typecase label 9723 (string label) 9724 (vector 9725 (string-join (mapcar (lambda (part) 9726 (-let (((&InlayHintLabelPart :value) part)) 9727 value)) 9728 label))))) 9729 9730 (defun lsp-update-inlay-hints (start end) 9731 (lsp-request-async 9732 "textDocument/inlayHint" 9733 (lsp-make-inlay-hints-params 9734 :text-document (lsp--text-document-identifier) 9735 :range (lsp-make-range :start 9736 (lsp-point-to-position start) 9737 :end 9738 (lsp-point-to-position end))) 9739 (lambda (res) 9740 (lsp--remove-overlays 'lsp-inlay-hint) 9741 (dolist (hint res) 9742 (-let* (((&InlayHint :label :position :kind? :padding-left? :padding-right?) hint) 9743 (kind (or kind? lsp/inlay-hint-kind-type-hint)) 9744 (label (lsp--label-from-inlay-hints-response label)) 9745 (pos (lsp--position-to-point position)) 9746 (overlay (make-overlay pos pos nil 'front-advance 'end-advance))) 9747 (when (stringp label) 9748 (overlay-put overlay 'lsp-inlay-hint t) 9749 (overlay-put overlay 'before-string 9750 (format "%s%s%s" 9751 (if padding-left? " " "") 9752 (propertize (lsp--format-inlay label kind) 9753 'font-lock-face (lsp--face-for-inlay kind)) 9754 (if padding-right? " " ""))))))) 9755 :mode 'tick)) 9756 9757 (define-minor-mode lsp-inlay-hints-mode 9758 "Mode for displaying inlay hints." 9759 :lighter nil 9760 (cond 9761 ((and lsp-inlay-hints-mode lsp--buffer-workspaces) 9762 (add-hook 'lsp-on-idle-hook #'lsp--update-inlay-hints nil t) 9763 (when lsp-update-inlay-hints-on-scroll 9764 (add-to-list (make-local-variable 'window-scroll-functions) 9765 #'lsp--update-inlay-hints-scroll-function))) 9766 (t 9767 (lsp--remove-overlays 'lsp-inlay-hint) 9768 (remove-hook 'lsp-on-idle-hook #'lsp--update-inlay-hints t) 9769 (setf window-scroll-functions 9770 (delete #'lsp--update-inlay-hints-scroll-function window-scroll-functions))))) 9771 9772 9773 9774 ;;;###autoload 9775 (defun lsp-start-plain () 9776 "Start `lsp-mode' using minimal configuration using the latest `melpa' version 9777 of the packages. 9778 9779 In case the major-mode that you are using for " 9780 (interactive) 9781 (let ((start-plain (make-temp-file "plain" nil ".el"))) 9782 (url-copy-file "https://raw.githubusercontent.com/emacs-lsp/lsp-mode/master/scripts/lsp-start-plain.el" 9783 start-plain t) 9784 (async-shell-command 9785 (format "%s -q -l %s %s" 9786 (expand-file-name invocation-name invocation-directory) 9787 start-plain 9788 (or (buffer-file-name) "")) 9789 (generate-new-buffer " *lsp-start-plain*")))) 9790 9791 9792 9793 (provide 'lsp-mode) 9794 ;;; lsp-mode.el ends here