eglot¶
I use eglot as my language client since it is available out of the box and integrates nicely with core packages like xref
init.el
emacs r1
(use-package alc-eglot
:bind (("<f2>" . eglot-rename)
("C-c e" . alc-eglot-menu))
:custom
(eglot-autoshutdown t)
(eglot-extend-to-xref t)
(eglot-events-buffer-config '(:size 0)) ;; I use `lsp-devtools` to inspect traffic.
:config
(advice-add 'eglot--workspace-configuration-plist :around #'alc-eglot-fix-workspace-configuration-scope)
(add-to-list 'display-buffer-alist
'("\\*EGLOT workspace configuration\\*"
(display-buffer-below-selected)
(dedicated . t))))
You see advice out there for bumping the default value of read-process-output-max to reduce the overhead of reading output from LSP servers etc.
According to the docstring this should not exceed the value of /proc/sys/fs/pipe-max-size
init.el
emacs r1
(setq read-process-output-max
(max 1048576 ; 1MB
(string-to-number (with-temp-buffer
(ignore-errors (insert-file "/proc/sys/fs/pipe-max-size"))
(buffer-string)))))
Transient¶
It’s nice to have a transient menu from which we can launch things.
lisp/alc-eglot.el
emacs r1
(require 'eglot)
(require 'transient)
(transient-define-prefix alc-eglot-menu ()
"Transient menu for launching eglot things."
["Code Actions"
("a" "All" eglot-code-actions)
("i" "Organise Imports" eglot-code-action-organize-imports)
("q" "Quickfix" eglot-code-action-quickfix)]
["Server Process"
("c" "Config" eglot-show-workspace-configuration)
("E" "Stderr" eglot-stderr-buffer)
("r" "Restart" alc-eglot-restart)])
lsp-devtools¶
Often I want to wrap some server with the lsp-devtools agent. What’s nice is that we can simply advise the function eglot uses to determine how to connect with the server
lisp/alc-eglot.el
emacs r1
(defun alc-eglot-wrap-server-in-lsp-devtools (oldfun &rest args)
"Wrap the server config returned from eglot in the lsp-devtools agent."
(if-let ((lsp-devtools (executable-find "lsp-devtools")))
(pcase-let ((`(,modes ,project ,class ,contact ,lang-ids) (apply oldfun args)))
`(,modes ,project ,class ,(append `(,lsp-devtools "agent" "--") contact) ,lang-ids))
(apply oldfun args)))
I’m sure there’s loads of fun to be had coming up with a nice workflow to determine which projects/servers I wish to wrap at any given time however, for now I just run the following commands to enable/disable wrapping as needed.
emacs r1
(advice-add #'eglot--guess-contact :around #'alc-eglot-wrap-server-in-lsp-devtools)
(advice-remove #'eglot--guess-contact #'alc-eglot-wrap-server-in-lsp-devtools)
Workspace Configuration¶
Convincing eglot to apply the correct workspace/configuration settings for the current server and project is the single most frustrating part of using it…
One issue is making sure eglot evaluates settings at the right scope
lisp/alc-eglot.el
emacs r1
(require 's)
(defun alc-eglot-fix-workspace-configuration-scope (orig-fun server &optional path)
"Fix the scopeUri of a workspace/configuration request.
When eglot handles a workspace/configuration request with an
associated scopeUri it uses the `file-name-directory' function to
determine the directory from which to resolve the configuration
values.
This causes an issue for servers like esbonio and pyright which
set the scopeUri to a directory. For `file-name-directory' to
treat the path as a directory it must include a trailing slash, a
convention which these servers do not follow.
Therefore `file-name-directory' treats the path as a file and
removes the final component, causing eglot to resolve the
configuration relative to the wrong directory.
This function fixes the issue by advising the
`eglot--workspace-configuration-plist' function, ensuring that
paths referencing directories include the trailing slash."
(if (and path
(file-directory-p path)
(not (s-ends-with? "/" path)))
(funcall orig-fun server (concat path "/"))
(funcall orig-fun server path)))
Utilities¶
Helper function to (re)start the server in the current buffer.
lisp/alc-eglot.el
emacs r1
(defun alc-eglot-restart ()
"(Re)start the server in the current buffer."
(interactive)
(if (eglot-current-server)
(call-interactively 'eglot-reconnect)
(call-interactively 'eglot)))