eglot¶
I use eglot as my language client since it is available out of the box and integrates nicely with core packages like xref
emacs
init.el
(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
emacs
init.el
(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.
emacs
lisp/alc-eglot.el
(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
emacs
lisp/alc-eglot.el
(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.
(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
emacs
lisp/alc-eglot.el
(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.
emacs
lisp/alc-eglot.el
(defun alc-eglot-restart ()
"(Re)start the server in the current buffer."
(interactive)
(if (eglot-current-server)
(call-interactively 'eglot-reconnect)
(call-interactively 'eglot)))