From 6a163fd5c198bdbfa56154a0fac138f59dffa1da Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Thu, 28 May 2020 11:03:49 +0200 Subject: [PATCH] [review] create +lsp.el and +eglot.el files This means we can remove a lot of `:unless (featurep! +eglot)` --- modules/tools/lsp/+eglot.el | 23 ++++ modules/tools/lsp/+lsp.el | 183 +++++++++++++++++++++++++++++++ modules/tools/lsp/config.el | 213 +----------------------------------- 3 files changed, 209 insertions(+), 210 deletions(-) create mode 100644 modules/tools/lsp/+eglot.el create mode 100644 modules/tools/lsp/+lsp.el diff --git a/modules/tools/lsp/+eglot.el b/modules/tools/lsp/+eglot.el new file mode 100644 index 000000000..a8ee9ca96 --- /dev/null +++ b/modules/tools/lsp/+eglot.el @@ -0,0 +1,23 @@ +;;; tools/lsp/+eglot.el -*- lexical-binding: t; -*- + +(use-package! eglot + :commands (eglot-ensure eglot) + :when (featurep! +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) + ;; Eager loading which is okay-ish since we want eglot to feed flycheck as soon as possible. + (load! "flycheck-eglot.el") + (require 'flycheck-eglot)) + (set-lookup-handlers! 'eglot--managed-mode + :documentation #'+eglot/documentation-lookup-handler + :definition '(xref-find-definitions :async t) + :references '(xref-find-references :async t))) diff --git a/modules/tools/lsp/+lsp.el b/modules/tools/lsp/+lsp.el new file mode 100644 index 000000000..5d271a66f --- /dev/null +++ b/modules/tools/lsp/+lsp.el @@ -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) diff --git a/modules/tools/lsp/config.el b/modules/tools/lsp/config.el index b19805d1a..753cf32f5 100644 --- a/modules/tools/lsp/config.el +++ b/modules/tools/lsp/config.el @@ -8,213 +8,6 @@ This delay prevents premature server shutdown when a user still intends on working on that project after closing the last buffer.") ;; TODO : set eglot-events-buffer-size to nil in doom-debug-mode - -;; -;;; Packages - -(use-package! eglot - :when (featurep! +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) - ;; Eager loading which is okay-ish since we want eglot to feed flycheck as soon as possible. - (load! "flycheck-eglot.el") - (require 'flycheck-eglot)) - (set-lookup-handlers! 'eglot--managed-mode - :documentation #'+eglot/documentation-lookup-handler - :definition '(xref-find-definitions :async t) - :references '(xref-find-references :async t))) - -(use-package! lsp-mode - :unless (featurep! +eglot) - :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 - :unless (featurep! +eglot) - :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 - :unless (featurep! +eglot) - :when (featurep! :completion helm) - :commands helm-lsp-workspace-symbol helm-lsp-global-workspace-symbol) - - -(use-package! lsp-ivy - :unless (featurep! +eglot) - :when (featurep! :completion ivy) - :commands lsp-ivy-workspace-symbol lsp-ivy-global-workspace-symbol) +(if (featurep! +eglot) + (load! "+eglot.el") + (load! "+lsp.el"))