:title: eglot :date: 2025-03-05 :tags: emacs :identifier: 20250305T195742 :signature: 5=3 ``eglot`` ========= I use ``eglot`` as my language client since it is available out of the box and integrates nicely with core packages like ``xref`` .. code-block:: elisp :project: emacs :filename: init.el (use-package alc-eglot :bind (("" . 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`` .. code-block:: elisp :project: emacs :filename: 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. .. code-block:: elisp :project: emacs :filename: 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 .. code-block:: elisp :project: emacs :filename: 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. .. code-block:: elisp (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 .. code-block:: elisp :project: emacs :template: elisp-module :filename: 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. .. code-block:: elisp :project: emacs :filename: 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)))