Merge pull request #3020 from gagbo/feature/eglot-support
Add support for eglot as LSP client implementation
This commit is contained in:
commit
75b6e11f56
24 changed files with 454 additions and 235 deletions
|
@ -73,7 +73,7 @@ Check out [the FAQ][FAQ] for answers to common questions about the project.
|
|||
- Optional vim emulation powered by [evil-mode], including ports of popular vim
|
||||
plugins like [vim-sneak], [vim-easymotion], [vim-unimpaired] and
|
||||
[more][ported-vim-plugins]!
|
||||
- Opt-in LSP integration for many languages, using [lsp-mode].
|
||||
- Opt-in LSP integration for many languages, using [lsp-mode] or [eglot]
|
||||
- Support for *many* programming languages. Includes syntax highlighting,
|
||||
linters/checker integration, inline code evaluation, code completion (where
|
||||
possible), REPLs, documentation lookups, snippets, and more!
|
||||
|
@ -240,6 +240,7 @@ kind!
|
|||
[helm]: https://github.com/emacs-helm/helm
|
||||
[ivy]: https://github.com/abo-abo/swiper
|
||||
[lsp-mode]: https://github.com/emacs-lsp/lsp-mode
|
||||
[eglot]: https://github.com/joaotavora/eglot
|
||||
[nix]: https://nixos.org
|
||||
[ported-vim-plugins]: modules/editor/evil/README.org#ported-vim-plugins
|
||||
[ripgrep]: https://github.com/BurntSushi/ripgrep
|
||||
|
|
|
@ -169,7 +169,7 @@ Small modules that give Emacs access to external tools & services.
|
|||
+ gist - TODO
|
||||
+ [[file:../modules/tools/lookup/README.org][lookup]] =+dictionary +docsets +offline +xwidget= - Universal jump-to & documentation lookup
|
||||
backend
|
||||
+ [[file:../modules/tools/lsp/README.org][lsp]] =+peek= - TODO
|
||||
+ [[file:../modules/tools/lsp/README.org][lsp]] =+peek +eglot= - Installation and configuration of language server protocol client (lsp-mode or eglot)
|
||||
+ macos - TODO
|
||||
+ [[file:../modules/tools/magit/README.org][magit]] =+forge= - TODO
|
||||
+ make - TODO
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
:desc "List errors" "x" #'flymake-show-diagnostics-buffer
|
||||
(:when (featurep! :checkers syntax)
|
||||
:desc "List errors" "x" #'flycheck-list-errors)
|
||||
(:when (featurep! :tools lsp)
|
||||
(:when (and (featurep! :tools lsp) (not (featurep! :tools lsp +eglot)))
|
||||
:desc "LSP Code actions" "a" #'lsp-execute-code-action
|
||||
:desc "LSP Organize imports" "i" #'lsp-organize-imports
|
||||
:desc "LSP Rename" "r" #'lsp-rename
|
||||
|
@ -52,7 +52,13 @@
|
|||
:desc "Jump to symbol in any workspace" "J" #'lsp-ivy-global-workspace-symbol)
|
||||
(:when (featurep! :completion helm)
|
||||
:desc "Jump to symbol in current workspace" "j" #'helm-lsp-workspace-symbol
|
||||
:desc "Jump to symbol in any workspace" "J" #'helm-lsp-global-workspace-symbol)))
|
||||
:desc "Jump to symbol in any workspace" "J" #'helm-lsp-global-workspace-symbol))
|
||||
(:when (featurep! :tools lsp +eglot)
|
||||
:desc "LSP Execute code action" "a" #'eglot-code-actions
|
||||
:desc "LSP Format buffer/region" "F" #'eglot-format
|
||||
:desc "LSP Rename" "r" #'eglot-rename
|
||||
:desc "LSP Find declaration" "j" #'eglot-find-declaration
|
||||
:desc "LSP Find implementation" "J" #'eglot-find-implementation))
|
||||
|
||||
;;; <leader> f --- file
|
||||
(:prefix-map ("f" . "file")
|
||||
|
|
|
@ -343,6 +343,24 @@
|
|||
|
||||
;;; <leader> c --- code
|
||||
(:prefix-map ("c" . "code")
|
||||
(:unless (featurep! :tools lsp +eglot)
|
||||
:desc "LSP Execute code action" "a" #'lsp-execute-code-action
|
||||
:desc "LSP Organize imports" "i" #'lsp-organize-imports
|
||||
(:when (featurep! :completion ivy)
|
||||
:desc "Jump to symbol in current workspace" "j" #'lsp-ivy-workspace-symbol
|
||||
:desc "Jump to symbol in any workspace" "J" #'lsp-ivy-global-workspace-symbol)
|
||||
(:when (featurep! :completion helm)
|
||||
:desc "Jump to symbol in current workspace" "j" #'helm-lsp-workspace-symbol
|
||||
:desc "Jump to symbol in any workspace" "J" #'helm-lsp-global-workspace-symbol)
|
||||
:desc "LSP Rename" "r" #'lsp-rename
|
||||
(:after lsp-mode
|
||||
:desc "LSP" "l" lsp-command-map))
|
||||
(:when (featurep! :tools lsp +eglot)
|
||||
:desc "LSP Execute code action" "a" #'eglot-code-actions
|
||||
:desc "LSP Format buffer/region" "F" #'eglot-format
|
||||
:desc "LSP Rename" "r" #'eglot-rename
|
||||
:desc "LSP Find declaration" "j" #'eglot-find-declaration
|
||||
:desc "LSP Find implementation" "J" #'eglot-find-implementation)
|
||||
:desc "Compile" "c" #'compile
|
||||
:desc "Recompile" "C" #'recompile
|
||||
:desc "Jump to definition" "d" #'+lookup/definition
|
||||
|
@ -350,19 +368,7 @@
|
|||
:desc "Evaluate buffer/region" "e" #'+eval/buffer-or-region
|
||||
:desc "Evaluate & replace region" "E" #'+eval:replace-region
|
||||
:desc "Format buffer/region" "f" #'+format/region-or-buffer
|
||||
(:when (featurep! :completion ivy)
|
||||
:desc "Jump to symbol in current workspace" "j" #'lsp-ivy-workspace-symbol
|
||||
:desc "Jump to symbol in any workspace" "J" #'lsp-ivy-global-workspace-symbol)
|
||||
(:when (featurep! :completion helm)
|
||||
:desc "Jump to symbol in current workspace" "j" #'helm-lsp-workspace-symbol
|
||||
:desc "Jump to symbol in any workspace" "J" #'helm-lsp-global-workspace-symbol)
|
||||
:desc "Jump to documentation" "k" #'+lookup/documentation
|
||||
(:when (featurep! :tools lsp)
|
||||
:desc "LSP Execute code action" "a" #'lsp-execute-code-action
|
||||
:desc "LSP Organize imports" "i" #'lsp-organize-imports
|
||||
:desc "LSP Rename" "r" #'lsp-rename
|
||||
(:after lsp-mode
|
||||
:desc "LSP" "l" lsp-command-map))
|
||||
:desc "Send to repl" "s" #'+eval/send-region-to-repl
|
||||
:desc "Delete trailing whitespace" "w" #'delete-trailing-whitespace
|
||||
:desc "Delete trailing newlines" "W" #'doom/delete-trailing-newlines
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
- [[#configure][Configure]]
|
||||
- [[#project-compile-settings][Project compile settings]]
|
||||
- [[#known-issues-with-bear-on-macos][Known issues with bear on macOS]]
|
||||
- [[#appendix][Appendix]]
|
||||
- [[#eglot-specific-bindings][Eglot specific bindings]]
|
||||
|
||||
* Description
|
||||
This module adds support for the C-family of languages: C, C++, and Objective-C.
|
||||
|
@ -42,7 +44,7 @@ This module adds support for the C-family of languages: C, C++, and Objective-C.
|
|||
+ [[https://github.com/jimhourihan/glsl-mode][glsl-mode]]*
|
||||
+ [[https://github.com/guidoschmidt/company-glsl][company-glsl]]*
|
||||
+ =+lsp=
|
||||
+ [[https://github.com/MaskRay/emacs-ccls][ccls]]
|
||||
+ [[https://github.com/MaskRay/emacs-ccls][ccls]] if =:tools lsp= has *no* =+eglot= flag
|
||||
+ =-lsp=
|
||||
+ [[https://github.com/Sarcasm/irony-mode][irony]]
|
||||
+ [[https://github.com/ikirill/irony-eldoc][irony-eldoc]]
|
||||
|
@ -173,3 +175,12 @@ bear gmake
|
|||
Additional info:
|
||||
+ [[https://github.com/rizsotto/Bear/issues/158][Empty compilation database with compiler in /usr/local]]
|
||||
+ [[https://github.com/rizsotto/Bear/issues/152][Workaround for 'Empty compilation database on OS X Captain]]
|
||||
|
||||
* Appendix
|
||||
** Eglot specific bindings
|
||||
When using =+lsp= and =:tools (lsp +eglot)=, lsp-mode is replaced with eglot,
|
||||
and an additional function to get inheritance type hierarchy is added
|
||||
| Binding | Description |
|
||||
|------------------------------+--------------------------------------------------|
|
||||
| ~<localleader> c t~ | ~Display inheritance type hierarchy (upwards)~ |
|
||||
| ~<prefix> <localleader> c t~ | ~Display inheritance type hierarchy (downwards)~ |
|
||||
|
|
|
@ -122,6 +122,34 @@ simpler."
|
|||
#'rtags-imenu
|
||||
#'imenu)))
|
||||
|
||||
;; Eglot specific helper, courtesy of MaskRay
|
||||
;;;###autoload
|
||||
(defun +cc/eglot-ccls-inheritance-hierarchy (&optional derived)
|
||||
"Show inheritance hierarchy for the thing at point.
|
||||
If DERIVED is non-nil (interactively, with prefix argument), show
|
||||
the children of class at point."
|
||||
(interactive "P")
|
||||
(if-let* ((res (jsonrpc-request
|
||||
(eglot--current-server-or-lose)
|
||||
:$ccls/inheritance
|
||||
(append (eglot--TextDocumentPositionParams)
|
||||
`(:derived ,(if derived t :json-false))
|
||||
'(:levels 100) '(:hierarchy t))))
|
||||
(tree (list (cons 0 res))))
|
||||
(with-help-window "*ccls inheritance*"
|
||||
(with-current-buffer standard-output
|
||||
(while tree
|
||||
(pcase-let ((`(,depth . ,node) (pop tree)))
|
||||
(cl-destructuring-bind (&key uri range) (plist-get node :location)
|
||||
(insert (make-string depth ?\ ) (plist-get node :name) "\n")
|
||||
(make-text-button (+ (point-at-bol 0) depth) (point-at-eol 0)
|
||||
'action (lambda (_arg)
|
||||
(interactive)
|
||||
(find-file (eglot--uri-to-path uri))
|
||||
(goto-char (car (eglot--range-region range)))))
|
||||
(cl-loop for child across (plist-get node :children)
|
||||
do (push (cons (1+ depth) child) tree)))))))
|
||||
(eglot--error "Hierarchy unavailable")))
|
||||
|
||||
;;
|
||||
;; Hooks
|
||||
|
|
|
@ -236,9 +236,25 @@ If rtags or rdm aren't available, fail silently instead of throwing a breaking e
|
|||
(setq-local company-lsp-cache-candidates nil)
|
||||
(lsp!))))
|
||||
|
||||
(when (and (featurep! +lsp) (featurep! :tools lsp +eglot))
|
||||
;; Map eglot specific helper
|
||||
(map! :localleader
|
||||
:after cc-mode
|
||||
:map c++-mode-map
|
||||
:n :desc "Show type inheritance hierarchy" "ct" #'+cc/eglot-ccls-inheritance-hierarchy)
|
||||
|
||||
;; NOTE : This setting is untested yet
|
||||
(after! eglot
|
||||
;; IS-MAC custom configuration
|
||||
(when IS-MAC
|
||||
(add-to-list 'eglot-workspace-configuration
|
||||
((:ccls . ((:clang . ,(list :extraArgs ["-isystem/Library/Developer/CommandLineTools/usr/include/c++/v1"
|
||||
"-isystem/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include"
|
||||
"-isystem/usr/local/include"]
|
||||
:resourceDir (string-trim (shell-command-to-string "clang -print-resource-dir")))))))))))
|
||||
|
||||
(use-package! ccls
|
||||
:when (featurep! +lsp)
|
||||
:when (and (featurep! +lsp) (not (featurep! :tools lsp +eglot)))
|
||||
:after lsp
|
||||
:init
|
||||
(after! projectile
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
:pin "404cd0694a")))
|
||||
|
||||
(if (featurep! +lsp)
|
||||
(package! ccls :pin "17ec7bb4cf")
|
||||
(unless (featurep! :tools lsp +eglot)
|
||||
;; ccls package is necessary only for lsp-mode.
|
||||
(package! ccls :pin "17ec7bb4cf"))
|
||||
(when (package! irony :pin "5f75fc0c92")
|
||||
(package! irony-eldoc :pin "0df5831eaa")
|
||||
(when (featurep! :checkers syntax)
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
(when (featurep! +dante)
|
||||
(package! dante :pin "4955bc7363")
|
||||
(package! attrap :pin "4cf3e4a162"))
|
||||
(when (or (featurep! +lsp)
|
||||
(when (or (and (featurep! +lsp) (not (featurep! :tools lsp +eglot)))
|
||||
(featurep! +ghcide))
|
||||
(package! lsp-haskell :pin "582fa27c88"))
|
||||
|
|
|
@ -22,7 +22,10 @@ called.")
|
|||
python-indent-guess-indent-offset-verbose nil)
|
||||
|
||||
(when (featurep! +lsp)
|
||||
(add-hook 'python-mode-local-vars-hook #'lsp!))
|
||||
(add-hook 'python-mode-local-vars-hook #'lsp!)
|
||||
;; Use "mspyls" in eglot if in PATH
|
||||
(when (executable-find "Microsoft.Python.LanguageServer")
|
||||
(set-eglot-client! 'python-mode '("Microsoft.Python.LanguageServer"))))
|
||||
:config
|
||||
(set-repl-handler! 'python-mode #'+python/open-repl :persist t)
|
||||
(set-docsets! 'python-mode "Python 3" "NumPy" "SciPy")
|
||||
|
@ -98,6 +101,7 @@ called.")
|
|||
"Enable `anaconda-mode' if `lsp-mode' is absent and
|
||||
`python-shell-interpreter' is present."
|
||||
(unless (or (bound-and-true-p lsp-mode)
|
||||
(bound-and-true-p eglot--managed-mode)
|
||||
(bound-and-true-p lsp--buffer-deferred)
|
||||
(not (executable-find python-shell-interpreter)))
|
||||
(anaconda-mode +1))))
|
||||
|
@ -286,7 +290,7 @@ called.")
|
|||
|
||||
|
||||
(use-package! lsp-python-ms
|
||||
:when (featurep! +lsp)
|
||||
:when (and (featurep! +lsp) (not (featurep! :tools lsp +eglot)))
|
||||
:after lsp-clients
|
||||
:preface
|
||||
(after! python
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(package! flycheck-cython :pin "ecc4454d35")))
|
||||
|
||||
;; LSP
|
||||
(when (featurep! +lsp)
|
||||
(when (and (featurep! +lsp) (not (featurep! :tools lsp +eglot)))
|
||||
(package! lsp-python-ms :pin "5d0c799099"))
|
||||
|
||||
;; Programming environment
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
(after! rustic-flycheck
|
||||
(add-to-list 'flycheck-checkers 'rustic-clippy)))
|
||||
|
||||
(when (featurep! +lsp)
|
||||
(if (featurep! :tools lsp +eglot)
|
||||
(setq rustic-lsp-client 'eglot)
|
||||
(setq rustic-lsp-client 'lsp-mode)))
|
||||
|
||||
(map! :map rustic-mode-map
|
||||
:localleader
|
||||
(:prefix ("b" . "build")
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
|
||||
|
||||
(use-package! dap-mode
|
||||
:when (featurep! +lsp)
|
||||
:when (and (featurep! +lsp) (not (featurep! :tools lsp +eglot)))
|
||||
:hook (dap-mode . dap-tooltip-mode)
|
||||
:after lsp-mode
|
||||
:demand t
|
||||
|
|
4
modules/tools/debugger/doctor.el
Normal file
4
modules/tools/debugger/doctor.el
Normal file
|
@ -0,0 +1,4 @@
|
|||
;;; tools/debugger/doctor.el -*- lexical-binding: t; -*-
|
||||
|
||||
(when (and (featurep! +lsp) (featurep! :tools lsp +eglot))
|
||||
(warn! "+lsp flag is not compatible with :tools (lsp +eglot). Choose only one of (eglot or dap-mode) please"))
|
19
modules/tools/lsp/+eglot.el
Normal file
19
modules/tools/lsp/+eglot.el
Normal file
|
@ -0,0 +1,19 @@
|
|||
;;; tools/lsp/+eglot.el -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package! eglot
|
||||
:commands (eglot-ensure eglot)
|
||||
:init
|
||||
(setq eglot-sync-connect 1
|
||||
eglot-connect-timeout 10
|
||||
eglot-autoshutdown t
|
||||
eglot-send-changes-idle-time 0.5
|
||||
;; NOTE: Do NOT set eglot-auto-display-help-buffer to t.
|
||||
;; With popup-rule! :select t, eglot will steal focus from the source code very often.
|
||||
eglot-auto-display-help-buffer nil)
|
||||
:config
|
||||
(set-popup-rule! "^\\*eglot-help" :size 0.35 :quit t :select t)
|
||||
(when (featurep! :checkers syntax)
|
||||
(after! flycheck
|
||||
(load! "autoload/flycheck-eglot")))
|
||||
(set-lookup-handlers! 'eglot--managed-mode
|
||||
:documentation #'+eglot/documentation-lookup-handler))
|
183
modules/tools/lsp/+lsp.el
Normal file
183
modules/tools/lsp/+lsp.el
Normal file
|
@ -0,0 +1,183 @@
|
|||
;;; tools/lsp/+lsp.el -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package! lsp-mode
|
||||
:commands lsp-install-server
|
||||
:init
|
||||
(setq lsp-session-file (concat doom-etc-dir "lsp-session"))
|
||||
;; Auto-kill LSP server after last workspace buffer is killed.
|
||||
(setq lsp-keep-workspace-alive nil)
|
||||
;; Let `flycheck-check-syntax-automatically' determine this.
|
||||
(setq lsp-flycheck-live-reporting nil)
|
||||
;; For `lsp-clients'
|
||||
(setq lsp-server-install-dir (concat doom-etc-dir "lsp/")
|
||||
lsp-intelephense-storage-path (concat doom-cache-dir "lsp-intelephense/"))
|
||||
|
||||
(when (featurep! :config default +bindings)
|
||||
;; Let doom bind the lsp keymap.
|
||||
(setq lsp-keymap-prefix nil))
|
||||
|
||||
;; Disable LSP's superfluous, expensive and/or debatably unnecessary features.
|
||||
;; Some servers implement these poorly. Better to just rely on Emacs' native
|
||||
;; mechanisms and make these opt-in.
|
||||
(setq lsp-enable-folding nil
|
||||
;; HACK Fix #2911, until it is resolved upstream. Links come in
|
||||
;; asynchronously from the server, but lsp makes no effort to
|
||||
;; "select" the original buffer before laying them down, so they
|
||||
;; could be rendered in the wrong buffer (like the minibuffer).
|
||||
lsp-enable-links nil
|
||||
;; Potentially slow
|
||||
lsp-enable-file-watchers nil
|
||||
lsp-enable-text-document-color nil
|
||||
lsp-enable-semantic-highlighting nil
|
||||
;; Don't modify our code without our permission
|
||||
lsp-enable-indentation nil
|
||||
lsp-enable-on-type-formatting nil
|
||||
;; capf is the preferred completion mechanism for lsp-mode now
|
||||
lsp-prefer-capf t)
|
||||
|
||||
:config
|
||||
(set-popup-rule! "^\\*lsp-help" :size 0.35 :quit t :select t)
|
||||
(set-lookup-handlers! 'lsp-mode :async t
|
||||
:documentation #'lsp-describe-thing-at-point
|
||||
:definition #'lsp-find-definition
|
||||
:implementations #'lsp-find-implementation
|
||||
:type-definition #'lsp-find-type-definition
|
||||
:references #'lsp-find-references)
|
||||
|
||||
;; TODO Lazy load these. They don't need to be loaded all at once unless the
|
||||
;; user uses `lsp-install-server'.
|
||||
(when lsp-auto-configure
|
||||
(mapc (lambda (package) (require package nil t))
|
||||
lsp-client-packages))
|
||||
|
||||
(defadvice! +lsp-init-a (&optional arg)
|
||||
"Enable `lsp-mode' in the current buffer.
|
||||
|
||||
Meant to gimp `lsp', which is too eager about installing LSP servers, or
|
||||
prompting to do so, or complaining about no LSP servers, or initializing
|
||||
lsp-ui-mode, company, yasnippet and flycheck. We want LSP to work only if the
|
||||
server is present, and for server installation to be a deliberate act by the
|
||||
end-user. Also, setting up these other packages are handled by their respective
|
||||
modules.
|
||||
|
||||
Also see:
|
||||
+ `+lsp-init-company-h' (on `lsp-mode-hook')
|
||||
+ `+lsp-init-flycheck-or-flymake-h' (on `lsp-mode-hook')
|
||||
|
||||
This also logs the resolved project root, if found, so we know where we are."
|
||||
:override #'lsp
|
||||
(interactive "P")
|
||||
(and (buffer-file-name (buffer-base-buffer))
|
||||
(require 'lsp-mode nil t)
|
||||
(setq-local
|
||||
lsp--buffer-workspaces
|
||||
(or (lsp--try-open-in-library-workspace)
|
||||
(lsp--try-project-root-workspaces
|
||||
(equal arg '(4))
|
||||
(and arg (not (equal arg 1))))))
|
||||
;; read-process-output-max is only available on recent
|
||||
;; development builds of Emacs 27 and above
|
||||
(or (not (boundp 'read-process-output-max))
|
||||
(setq-local read-process-output-max (* 1024 1024)))
|
||||
;; REVIEW LSP causes a lot of allocations, with or without Emacs 27+'s
|
||||
;; native JSON library, so we up the GC threshold to stave off
|
||||
;; GC-induced slowdowns/freezes.
|
||||
(setq-local gcmh-high-cons-threshold (* 2 gcmh-high-cons-threshold))
|
||||
(prog1 (lsp-mode 1)
|
||||
(setq-local lsp-buffer-uri (lsp--buffer-uri))
|
||||
;; Announce what project root we're using, for diagnostic purposes
|
||||
(if-let (root (lsp--calculate-root (lsp-session) (buffer-file-name)))
|
||||
(lsp--info "Guessed project root is %s" (abbreviate-file-name root))
|
||||
(lsp--info "Could not guess project root."))
|
||||
(lsp--info "Connected to %s."
|
||||
(apply #'concat
|
||||
(mapcar
|
||||
(lambda (it) (format "[%s]" (lsp--workspace-print it)))
|
||||
lsp--buffer-workspaces))))))
|
||||
|
||||
(when (featurep! :config default +bindings)
|
||||
(dolist (leader-key (list doom-leader-key doom-leader-alt-key))
|
||||
(let ((lsp-keymap-prefix (concat leader-key " c l")))
|
||||
(lsp-enable-which-key-integration))))
|
||||
|
||||
(add-hook! 'lsp-mode-hook
|
||||
(defun +lsp-init-company-h ()
|
||||
(if (not (bound-and-true-p company-mode))
|
||||
(add-hook 'company-mode-hook #'+lsp-init-company-h t t)
|
||||
;; Ensure `company-capf' is at the front of `company-backends'
|
||||
(setq-local company-backends
|
||||
(cons 'company-capf
|
||||
(remq 'company-capf company-backends)))
|
||||
(remove-hook 'company-mode-hook #'+lsp-init-company-h t)))
|
||||
(defun +lsp-init-flycheck-or-flymake-h ()
|
||||
"Set up flycheck-mode or flymake-mode, depending on `lsp-diagnostic-package'."
|
||||
(pcase lsp-diagnostic-package
|
||||
((or :auto 'nil) ; try flycheck, fall back to flymake
|
||||
(let ((lsp-diagnostic-package
|
||||
(if (require 'flycheck nil t) :flycheck :flymake)))
|
||||
(+lsp-init-flycheck-or-flymake-h)))
|
||||
((or :flymake 't)
|
||||
(lsp--flymake-setup))
|
||||
(:flycheck
|
||||
(let ((old-checker flycheck-checker))
|
||||
(lsp-flycheck-enable)
|
||||
;; Ensure file/dir local `flycheck-checker' is respected
|
||||
(when old-checker
|
||||
(setq-local flycheck-checker old-checker)
|
||||
(kill-local-variable 'flycheck-check-syntax-automatically)))))))
|
||||
|
||||
(defvar +lsp--deferred-shutdown-timer nil)
|
||||
(defadvice! +lsp-defer-server-shutdown-a (orig-fn &optional restart)
|
||||
"Defer server shutdown for a few seconds.
|
||||
This gives the user a chance to open other project files before the server is
|
||||
auto-killed (which is a potentially expensive process)."
|
||||
:around #'lsp--shutdown-workspace
|
||||
(if (or lsp-keep-workspace-alive
|
||||
restart
|
||||
(null +lsp-defer-shutdown)
|
||||
(= +lsp-defer-shutdown 0))
|
||||
(funcall orig-fn restart)
|
||||
(when (timerp +lsp--deferred-shutdown-timer)
|
||||
(cancel-timer +lsp--deferred-shutdown-timer))
|
||||
(setq +lsp--deferred-shutdown-timer
|
||||
(run-at-time
|
||||
(if (numberp +lsp-defer-shutdown) +lsp-defer-shutdown 3)
|
||||
nil (lambda (workspace)
|
||||
(let ((lsp--cur-workspace workspace))
|
||||
(unless (lsp--workspace-buffers lsp--cur-workspace)
|
||||
(funcall orig-fn))))
|
||||
lsp--cur-workspace))))
|
||||
|
||||
;; Don't prompt to restart LSP servers while quitting Emacs
|
||||
(add-hook! 'kill-emacs-hook (setq lsp-restart 'ignore)))
|
||||
|
||||
|
||||
(use-package! lsp-ui
|
||||
:hook (lsp-mode . lsp-ui-mode)
|
||||
:config
|
||||
(setq lsp-ui-doc-max-height 8
|
||||
lsp-ui-doc-max-width 35
|
||||
lsp-ui-sideline-ignore-duplicate t
|
||||
;; lsp-ui-doc is redundant with and more invasive than
|
||||
;; `+lookup/documentation'
|
||||
lsp-ui-doc-enable nil
|
||||
;; Don't show symbol definitions in the sideline. They are pretty noisy,
|
||||
;; and there is a bug preventing Flycheck errors from being shown (the
|
||||
;; errors flash briefly and then disappear).
|
||||
lsp-ui-sideline-show-hover nil)
|
||||
|
||||
(when (featurep! +peek)
|
||||
(set-lookup-handlers! 'lsp-ui-mode :async t
|
||||
:definition 'lsp-ui-peek-find-definitions
|
||||
:implementations 'lsp-ui-peek-find-implementation
|
||||
:references 'lsp-ui-peek-find-references)))
|
||||
|
||||
|
||||
(use-package! helm-lsp
|
||||
:when (featurep! :completion helm)
|
||||
:commands helm-lsp-workspace-symbol helm-lsp-global-workspace-symbol)
|
||||
|
||||
|
||||
(use-package! lsp-ivy
|
||||
:when (featurep! :completion ivy)
|
||||
:commands lsp-ivy-workspace-symbol lsp-ivy-global-workspace-symbol)
|
|
@ -56,10 +56,16 @@ As of this writing, this is the state of LSP support in Doom Emacs:
|
|||
** Module Flags
|
||||
+ =+peek= Use =lsp-ui-peek= when looking up definitions and references with
|
||||
functionality from the =:tools lookup= module.
|
||||
+ =+eglot= Use [[https://elpa.gnu.org/packages/eglot.html][Eglot]] instead of [[https://github.com/emacs-lsp/lsp-mode][LSP-mode]] to implement the LSP client in
|
||||
Emacs.
|
||||
|
||||
** Plugins
|
||||
+ [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]]
|
||||
+ [[https://github.com/emacs-lsp/lsp-ui][lsp-ui]]
|
||||
+ [[https://github.com/emacs-lsp/lsp-ivy][lsp-ivy]]
|
||||
+ [[https://github.com/emacs-lsp/helm-lsp][helm-lsp]]
|
||||
+ [[https://github.com/joaotavora/eglot][eglot]]
|
||||
|
||||
** Hacks
|
||||
+ ~lsp-mode~ has been modified not to automatically install missing LSP servers.
|
||||
This is done to adhere to our "Your system, your rules" mantra, which insist
|
||||
|
@ -75,15 +81,37 @@ You'll find a table that lists available language servers and how to install
|
|||
them [[https://github.com/emacs-lsp/lsp-mode#supported-languages][in the lsp-mode project README]]. The documentation of the module for your
|
||||
targeted language will contain brief instructions as well.
|
||||
|
||||
For eglot users, you can see the list of [[https://github.com/joaotavora/eglot/blob/master/README.md#connecting-to-a-server][default servers supported in the README]].
|
||||
There is also instructions to add another server easily.
|
||||
|
||||
* TODO Features
|
||||
** LSP-powered project search
|
||||
When =:completion ivy= or =:completion helm= is active, LSP is used to search a
|
||||
symbol indexed by the LSP server :
|
||||
Without the =+eglot= flag, and when =:completion ivy= or =:completion helm= is
|
||||
active, LSP is used to search a symbol indexed by the LSP server :
|
||||
| Keybind | Description |
|
||||
|-----------+-------------------------------------|
|
||||
| =SPC c j= | Jump to symbol in current workspace |
|
||||
| =SPC c J= | Jump to symbol in any workspace |
|
||||
** Differences between eglot and lsp-mode
|
||||
Entering the debate about which one to use would be useless. Doom provides an
|
||||
easy way to switch out lsp client implementations so you can test for yourself
|
||||
which one you prefer.
|
||||
|
||||
Mainly, from a code point of view, lsp-mode has a lot of custom code for UI
|
||||
(=lsp-ui-peek=, =lsp-ui-sideline=, ...), while eglot is more barebones with a
|
||||
closer integration with "more basic" emacs packages (=eldoc=, =xref=, ...).
|
||||
|
||||
* TODO Configuration
|
||||
|
||||
* TODO Troubleshooting
|
||||
** My language server is not found
|
||||
Check the entry in the [[../../../docs/faq.org][FAQ]] about "Doom can't find my executables/doesn't inherit
|
||||
the correct ~PATH~"
|
||||
** LSP/Eglot is not started automatically in my buffer
|
||||
Make sure that you added the =+lsp= flag to the language you're using too in
|
||||
your init.el :
|
||||
#+BEGIN_SRC diff
|
||||
:lang
|
||||
-python
|
||||
+(python +lsp)
|
||||
#+END_SRC
|
||||
|
|
9
modules/tools/lsp/autoload/common.el
Normal file
9
modules/tools/lsp/autoload/common.el
Normal file
|
@ -0,0 +1,9 @@
|
|||
;;; tools/lsp/autoload/common.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autodef
|
||||
(defun lsp! ()
|
||||
"Dispatch to call the currently used lsp client entrypoint"
|
||||
(interactive)
|
||||
(if (featurep! +eglot)
|
||||
(eglot-ensure)
|
||||
(lsp-deferred)))
|
19
modules/tools/lsp/autoload/eglot.el
Normal file
19
modules/tools/lsp/autoload/eglot.el
Normal file
|
@ -0,0 +1,19 @@
|
|||
;;; tools/lsp/autoload/eglot.el -*- lexical-binding: t; -*-
|
||||
;;;###if (featurep! +eglot)
|
||||
|
||||
;;;###autodef
|
||||
(defun set-eglot-client! (mode server-call)
|
||||
"Add SERVER-CALL list as a possible lsp server for given major MODE.
|
||||
|
||||
Example : (set-eglot-client! 'python-mode `(,(concat doom-etc-dir \"lsp/mspyls/Microsoft.Python.LanguageServer\")))"
|
||||
(after! eglot
|
||||
(add-to-list 'eglot-server-programs `(,mode . ,server-call))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +eglot/documentation-lookup-handler ()
|
||||
"Documentation lookup handler using eglot :document/hover handler.
|
||||
|
||||
Mostly a rewrite of `eglot-help-at-point', which should be used interactively."
|
||||
(interactive)
|
||||
(eglot-help-at-point)
|
||||
(display-buffer eglot--help-buffer))
|
56
modules/tools/lsp/autoload/flycheck-eglot.el
Normal file
56
modules/tools/lsp/autoload/flycheck-eglot.el
Normal file
|
@ -0,0 +1,56 @@
|
|||
;;; flycheck-eglot --- Hacky eglot support in flycheck -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
(defun flycheck-eglot--start (checker callback)
|
||||
"Clean up errors when done.
|
||||
|
||||
CHECKER is the checker (eglot).
|
||||
CALLBACK is the function that we need to call when we are done, on all the errors."
|
||||
(cl-labels
|
||||
((flymake-diag->flycheck-err
|
||||
(diag)
|
||||
(with-current-buffer (flymake--diag-buffer diag)
|
||||
(flycheck-error-new-at-pos
|
||||
(flymake--diag-beg diag)
|
||||
(pcase (flymake--diag-type diag)
|
||||
('eglot-note 'info)
|
||||
('eglot-warning 'warning)
|
||||
('eglot-error 'error)
|
||||
(_ (error "Unknown diagnostic type, %S" diag)))
|
||||
(flymake--diag-text diag)
|
||||
:end-pos (flymake--diag-end diag)
|
||||
:checker checker
|
||||
:buffer (current-buffer)
|
||||
:filename (buffer-file-name)))))
|
||||
;; NOTE: Setting up eglot to automatically create flycheck errors for the buffer.
|
||||
(eglot-flymake-backend
|
||||
(lambda (flymake-diags &rest _)
|
||||
(funcall callback
|
||||
'finished
|
||||
(mapcar #'flymake-diag->flycheck-err flymake-diags))))
|
||||
;; NOTE: Forcefully trigger a check in the buffer (function name is confusing)
|
||||
(flycheck-buffer)))
|
||||
|
||||
(defun flycheck-eglot--available-p ()
|
||||
(bound-and-true-p eglot--managed-mode))
|
||||
|
||||
(flycheck-define-generic-checker 'eglot
|
||||
"Report `eglot' diagnostics using `flycheck'."
|
||||
:start #'flycheck-eglot--start
|
||||
:predicate #'flycheck-eglot--available-p
|
||||
:modes '(prog-mode text-mode))
|
||||
|
||||
(push 'eglot flycheck-checkers)
|
||||
|
||||
(defun +doom-eglot-prefer-flycheck-h ()
|
||||
(when eglot--managed-mode
|
||||
(when-let ((current-checker (flycheck-get-checker-for-buffer)))
|
||||
(unless (equal current-checker 'eglot)
|
||||
(flycheck-add-next-checker 'eglot current-checker)))
|
||||
(flycheck-add-mode 'eglot major-mode)
|
||||
(flycheck-mode 1)
|
||||
(flymake-mode -1)))
|
||||
|
||||
(add-hook 'eglot--managed-mode-hook #'+doom-eglot-prefer-flycheck-h)
|
||||
|
||||
;;; flycheck-eglot.el ends here
|
|
@ -1,4 +1,5 @@
|
|||
;;; feature/lsp/autoload.el -*- lexical-binding: t; -*-
|
||||
;;; tools/lsp/autoload/lsp-mode.el -*- lexical-binding: t; -*-
|
||||
;;;###if (not (featurep! +eglot))
|
||||
|
||||
;;;###autodef
|
||||
(defun set-lsp-priority! (client priority)
|
||||
|
@ -9,9 +10,6 @@
|
|||
priority)
|
||||
(error "No LSP client named %S" client)))
|
||||
|
||||
;;;###autodef
|
||||
(defalias 'lsp! #'lsp-deferred)
|
||||
|
||||
;;;###autoload
|
||||
(defun +lsp/uninstall-server (dir)
|
||||
"Delete a LSP server from `lsp-server-install-dir'."
|
|
@ -7,188 +7,7 @@ workspace buffer is closed.
|
|||
This delay prevents premature server shutdown when a user still intends on
|
||||
working on that project after closing the last buffer.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Packages
|
||||
|
||||
(use-package! lsp-mode
|
||||
:commands lsp-install-server
|
||||
:init
|
||||
(setq lsp-session-file (concat doom-etc-dir "lsp-session"))
|
||||
;; Auto-kill LSP server after last workspace buffer is killed.
|
||||
(setq lsp-keep-workspace-alive nil)
|
||||
;; Let `flycheck-check-syntax-automatically' determine this.
|
||||
(setq lsp-flycheck-live-reporting nil)
|
||||
;; For `lsp-clients'
|
||||
(setq lsp-server-install-dir (concat doom-etc-dir "lsp/")
|
||||
lsp-intelephense-storage-path (concat doom-cache-dir "lsp-intelephense/"))
|
||||
|
||||
(when (featurep! :config default +bindings)
|
||||
;; Let doom bind the lsp keymap.
|
||||
(setq lsp-keymap-prefix nil))
|
||||
|
||||
;; Disable LSP's superfluous, expensive and/or debatably unnecessary features.
|
||||
;; Some servers implement these poorly. Better to just rely on Emacs' native
|
||||
;; mechanisms and make these opt-in.
|
||||
(setq lsp-enable-folding nil
|
||||
;; HACK Fix #2911, until it is resolved upstream. Links come in
|
||||
;; asynchronously from the server, but lsp makes no effort to
|
||||
;; "select" the original buffer before laying them down, so they
|
||||
;; could be rendered in the wrong buffer (like the minibuffer).
|
||||
lsp-enable-links nil
|
||||
;; Potentially slow
|
||||
lsp-enable-file-watchers nil
|
||||
lsp-enable-text-document-color nil
|
||||
lsp-enable-semantic-highlighting nil
|
||||
;; Don't modify our code without our permission
|
||||
lsp-enable-indentation nil
|
||||
lsp-enable-on-type-formatting nil
|
||||
;; capf is the preferred completion mechanism for lsp-mode now
|
||||
lsp-prefer-capf t)
|
||||
|
||||
:config
|
||||
(set-popup-rule! "^\\*lsp-help" :size 0.35 :quit t :select t)
|
||||
(set-lookup-handlers! 'lsp-mode :async t
|
||||
:documentation #'lsp-describe-thing-at-point
|
||||
:definition #'lsp-find-definition
|
||||
:implementations #'lsp-find-implementation
|
||||
:type-definition #'lsp-find-type-definition
|
||||
:references #'lsp-find-references)
|
||||
|
||||
;; TODO Lazy load these. They don't need to be loaded all at once unless the
|
||||
;; user uses `lsp-install-server'.
|
||||
(when lsp-auto-configure
|
||||
(mapc (lambda (package) (require package nil t))
|
||||
lsp-client-packages))
|
||||
|
||||
(defadvice! +lsp-init-a (&optional arg)
|
||||
"Enable `lsp-mode' in the current buffer.
|
||||
|
||||
Meant to gimp `lsp', which is too eager about installing LSP servers, or
|
||||
prompting to do so, or complaining about no LSP servers, or initializing
|
||||
lsp-ui-mode, company, yasnippet and flycheck. We want LSP to work only if the
|
||||
server is present, and for server installation to be a deliberate act by the
|
||||
end-user. Also, setting up these other packages are handled by their respective
|
||||
modules.
|
||||
|
||||
Also see:
|
||||
+ `+lsp-init-company-h' (on `lsp-mode-hook')
|
||||
+ `+lsp-init-flycheck-or-flymake-h' (on `lsp-mode-hook')
|
||||
|
||||
This also logs the resolved project root, if found, so we know where we are."
|
||||
:override #'lsp
|
||||
(interactive "P")
|
||||
(and (buffer-file-name (buffer-base-buffer))
|
||||
(require 'lsp-mode nil t)
|
||||
(setq-local
|
||||
lsp--buffer-workspaces
|
||||
(or (lsp--try-open-in-library-workspace)
|
||||
(lsp--try-project-root-workspaces
|
||||
(equal arg '(4))
|
||||
(and arg (not (equal arg 1))))))
|
||||
;; read-process-output-max is only available on recent
|
||||
;; development builds of Emacs 27 and above
|
||||
(or (not (boundp 'read-process-output-max))
|
||||
(setq-local read-process-output-max (* 1024 1024)))
|
||||
;; REVIEW LSP causes a lot of allocations, with or without Emacs 27+'s
|
||||
;; native JSON library, so we up the GC threshold to stave off
|
||||
;; GC-induced slowdowns/freezes.
|
||||
(setq-local gcmh-high-cons-threshold (* 2 gcmh-high-cons-threshold))
|
||||
(prog1 (lsp-mode 1)
|
||||
(setq-local lsp-buffer-uri (lsp--buffer-uri))
|
||||
;; Announce what project root we're using, for diagnostic purposes
|
||||
(if-let (root (lsp--calculate-root (lsp-session) (buffer-file-name)))
|
||||
(lsp--info "Guessed project root is %s" (abbreviate-file-name root))
|
||||
(lsp--info "Could not guess project root."))
|
||||
(lsp--info "Connected to %s."
|
||||
(apply #'concat
|
||||
(mapcar
|
||||
(lambda (it) (format "[%s]" (lsp--workspace-print it)))
|
||||
lsp--buffer-workspaces))))))
|
||||
|
||||
(when (featurep! :config default +bindings)
|
||||
(dolist (leader-key (list doom-leader-key doom-leader-alt-key))
|
||||
(let ((lsp-keymap-prefix (concat leader-key " c l")))
|
||||
(lsp-enable-which-key-integration))))
|
||||
|
||||
(add-hook! 'lsp-mode-hook
|
||||
(defun +lsp-init-company-h ()
|
||||
(if (not (bound-and-true-p company-mode))
|
||||
(add-hook 'company-mode-hook #'+lsp-init-company-h t t)
|
||||
;; Ensure `company-capf' is at the front of `company-backends'
|
||||
(setq-local company-backends
|
||||
(cons 'company-capf
|
||||
(remq 'company-capf company-backends)))
|
||||
(remove-hook 'company-mode-hook #'+lsp-init-company-h t)))
|
||||
(defun +lsp-init-flycheck-or-flymake-h ()
|
||||
"Set up flycheck-mode or flymake-mode, depending on `lsp-diagnostic-package'."
|
||||
(pcase lsp-diagnostic-package
|
||||
((or :auto 'nil) ; try flycheck, fall back to flymake
|
||||
(let ((lsp-diagnostic-package
|
||||
(if (require 'flycheck nil t) :flycheck :flymake)))
|
||||
(+lsp-init-flycheck-or-flymake-h)))
|
||||
((or :flymake 't)
|
||||
(lsp--flymake-setup))
|
||||
(:flycheck
|
||||
(let ((old-checker flycheck-checker))
|
||||
(lsp-flycheck-enable)
|
||||
;; Ensure file/dir local `flycheck-checker' is respected
|
||||
(when old-checker
|
||||
(setq-local flycheck-checker old-checker)
|
||||
(kill-local-variable 'flycheck-check-syntax-automatically)))))))
|
||||
|
||||
(defvar +lsp--deferred-shutdown-timer nil)
|
||||
(defadvice! +lsp-defer-server-shutdown-a (orig-fn &optional restart)
|
||||
"Defer server shutdown for a few seconds.
|
||||
This gives the user a chance to open other project files before the server is
|
||||
auto-killed (which is a potentially expensive process)."
|
||||
:around #'lsp--shutdown-workspace
|
||||
(if (or lsp-keep-workspace-alive
|
||||
restart
|
||||
(null +lsp-defer-shutdown)
|
||||
(= +lsp-defer-shutdown 0))
|
||||
(funcall orig-fn restart)
|
||||
(when (timerp +lsp--deferred-shutdown-timer)
|
||||
(cancel-timer +lsp--deferred-shutdown-timer))
|
||||
(setq +lsp--deferred-shutdown-timer
|
||||
(run-at-time
|
||||
(if (numberp +lsp-defer-shutdown) +lsp-defer-shutdown 3)
|
||||
nil (lambda (workspace)
|
||||
(let ((lsp--cur-workspace workspace))
|
||||
(unless (lsp--workspace-buffers lsp--cur-workspace)
|
||||
(funcall orig-fn))))
|
||||
lsp--cur-workspace))))
|
||||
|
||||
;; Don't prompt to restart LSP servers while quitting Emacs
|
||||
(add-hook! 'kill-emacs-hook (setq lsp-restart 'ignore)))
|
||||
|
||||
|
||||
(use-package! lsp-ui
|
||||
:hook (lsp-mode . lsp-ui-mode)
|
||||
:config
|
||||
(setq lsp-ui-doc-max-height 8
|
||||
lsp-ui-doc-max-width 35
|
||||
lsp-ui-sideline-ignore-duplicate t
|
||||
;; lsp-ui-doc is redundant with and more invasive than
|
||||
;; `+lookup/documentation'
|
||||
lsp-ui-doc-enable nil
|
||||
;; Don't show symbol definitions in the sideline. They are pretty noisy,
|
||||
;; and there is a bug preventing Flycheck errors from being shown (the
|
||||
;; errors flash briefly and then disappear).
|
||||
lsp-ui-sideline-show-hover nil)
|
||||
|
||||
(when (featurep! +peek)
|
||||
(set-lookup-handlers! 'lsp-ui-mode :async t
|
||||
:definition 'lsp-ui-peek-find-definitions
|
||||
:implementations 'lsp-ui-peek-find-implementation
|
||||
:references 'lsp-ui-peek-find-references)))
|
||||
|
||||
|
||||
(use-package! helm-lsp
|
||||
:when (featurep! :completion helm)
|
||||
:commands helm-lsp-workspace-symbol helm-lsp-global-workspace-symbol)
|
||||
|
||||
|
||||
(use-package! lsp-ivy
|
||||
:when (featurep! :completion ivy)
|
||||
:commands lsp-ivy-workspace-symbol lsp-ivy-global-workspace-symbol)
|
||||
;; TODO : set eglot-events-buffer-size to nil in doom-debug-mode
|
||||
(if (featurep! +eglot)
|
||||
(load! "+eglot")
|
||||
(load! "+lsp"))
|
||||
|
|
3
modules/tools/lsp/doctor.el
Normal file
3
modules/tools/lsp/doctor.el
Normal file
|
@ -0,0 +1,3 @@
|
|||
;;; tools/lsp/doctor.el -*- lexical-binding: t; -*-
|
||||
|
||||
(assert! (not (and (featurep! +eglot) (featurep! +peek))) "+eglot and +peek flags are not compatible. Peek uses lsp-mode, while Eglot is another package altogether for LSP.")
|
|
@ -1,9 +1,11 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; tools/lsp/packages.el
|
||||
|
||||
(package! lsp-mode :pin "81d62d581b21d847783831e6e5ca9d3c63fe9a4d")
|
||||
(package! lsp-ui :pin "271b47cb33f11915295911f7cf8575f8a82a5e1c")
|
||||
(when (featurep! :completion ivy)
|
||||
(if (featurep! +eglot)
|
||||
(package! eglot :pin "d99a4478a9")
|
||||
(package! lsp-mode :pin "81d62d581b21d847783831e6e5ca9d3c63fe9a4d")
|
||||
(package! lsp-ui :pin "271b47cb33f11915295911f7cf8575f8a82a5e1c")
|
||||
(when (featurep! :completion ivy)
|
||||
(package! lsp-ivy :pin "dce58b5509271bbedb53ba9d0278dcb563a43977"))
|
||||
(when (featurep! :completion helm)
|
||||
(package! helm-lsp :pin "6b5ce182d7c94c62b55b8f7d0c7e643b2c30e560"))
|
||||
(when (featurep! :completion helm)
|
||||
(package! helm-lsp :pin "6b5ce182d7c94c62b55b8f7d0c7e643b2c30e560")))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue