Moving from lsp-mode in GNU Emacs to Eglot
20 points by fanf
20 points by fanf
Eglot officially recommends that you start it by hand (cf), but I'm too lazy for that. Instead, as I did with lsp-mode, I arranged to start it automatically for local files in the relevant modes.
Depending on the language, this is catastrophically bad advice. LSP servers in many languages cannot be safely used on untrusted code; opening a Rust or Elixir file in a project controlled by an attacker can easily cause your machine to be compromised. Unless you're working in a language with a known-safe LSP server, never automatically activate LSP.
edit: citation: https://rust-analyzer.github.io/book/security.html
Another reason to not do this is that some language servers have hefty resource requirements. Taking a quick peek at a file in a moderately sized Rust project will result in a 4GB rust-analyzer process churning for several minutes and a 1GB+ target/debug/ directory.
Well, once you've run cargo build you're screwed anyway :D (I'm aware there's a big difference between autoloading an LSP and a command you explicitly run, but I suspect in practice there's not such a big difference...)
Yes, the problem is that in order to determine whether it's a good idea to run cargo build, you have to ... open the files to read them!
Setting up LSP to run automatically creates a circular dependency where determining whether the files is safe requires reading the files but reading the files requires running LSP, which requires determining whether the files are safe.
Oh, I'm weird and regularly open files with less! And for many purposes, I prefer using a web browser, even. (Yes, I know.)
(My colleagues look at me weird because I use grep, less, ... young people...)
For people who want this automation: lsp-mode's answer is to prompt you about enabling it (with an option to add a project to an allowlist)
If you have an "automatically do this" hook, you're probably like 10 lines of code away from having it prompt the user first (read-from-minibuffer is your friend!) and get most of the security benefits. "Do you trust this folder?" (along with using something like projectile to figure out a base dir)
my setup involves using lsp-mode's allow list but then wiping it each session. So I have to opt back in to a project once per Emacs boot.
(defun +r/lsp--kill-session-cache ()
(interactive)
(delete-file lsp-session-file))
;; just clear the lsp session every time
(add-hook! kill-emacs '+r/lsp--kill-session-cache)
I think originally I had done this because of perf reasons, with lsp-mode sometimes spinning up a bunch of things even before I opened certain projects.
Anyways the security risks are real but it's not too hard to get a nice workflow
Do you find that this leads to alert blindness? I feel like if I saw this every time I opened a new project, I would just get in the habit of pressing OK automatically with muscle memory. Given what we know about the psychology of warnings, I'm suspicious this would actually be effective.
It's certainly better than nothing tho! So thanks for sharing.
Obviously everyone has their own sort of levels of banner blindness. I've generally found it to not be the case for me (I mostly do this for perf reasons rather than security reasons but I think about it and don't automatically opt in). And the opt-in for me isn't Y/N but like 4 different options...
I think that it's worth pointing out that even though I have this, in practice if you "trust a project" but then pull in a commit from somebody else with the lsp running you could get owned in between anyways! So I'm merely reducing my risks but not really cleaning them up entirely
I generally work on stuff where I trust the authors of the commits I'm looking at in the first place. But one time I did work on something where I didn't trust the author, and was very glad to just not activate LSP or anything.
To be honest a big part of me barely trusts having "evil" code on my hard drive let alone opening it in an editor, so my risk analysis sort of starts at the git checkout phase of this whole process. That and doing things like piping to less instead of opening in Emacs.
tl;dr I don't feel alert blindness but I know this solution is imperfect. I am not looking at untrusted code very often.
IMO if you're looking at code that might be actively hostile, there's really no substitute for doing the entire thing inside an actual security boundary. Even git status can own you: https://github.com/justinsteven/advisories/blob/main/2022_git_buried_bare_repos_and_fsmonitor_various_abuses.md
I guess one difference is that the above only gets you if the exploit is in the repo itself rather that in a dependency. But I don't know how you'd stop yourself from becoming "alert blind" and just starting LSP out of habit.
My biggest pet peeve with eglot is that it doesn't expose most of the commands as functions, instead defining them in terms of Emacs interfaces, like xref. Because of that, if I have multiple xref backends, as I do in Clojure (one from CIDER, and one from clojure-lsp via lsp-mode), I prefer them to work through CIDER, because CIDER has runtime information about the actual state of the loaded code, and clojure-lsp static analysis sometimes may be desynchronized (especially for remote REPL workflows).
So with lsp-mode, I can call most of these things, like go-to-definition, as commands, and continue using xref. Dealing with that in Eglot is much harder because unregistering specific backends in xref is somewhat clumsy.
Other commands from lsp-mode are also missing in eglot, even though they're available through Emacs integration points similar to xref.
At another level, lsp-mode with lsp-ui is what I'd call a busy interface, with all sorts of things going on, and these days I've decided that I want a quieter LSP experience. […] I could have tamed lsp-ui more with additional settings and fiddling, but switching to Eglot took care of all of that all at once, with other benefits.
I tried lsp-mode once and it assaulted me with so many confusing popups and alerts that I uninstalled it immediately. Eglot is much more peaceful: I can turn it on and I can start using its features when I am ready for them. I’m interested to read how ~cks is approaching it from the other direction because of all the tips and alternatives he mentions.
I turned a whole bunch of lsp-mode stuff off so I have a pretty quiet interface. I tried switching to helot but it didn’t seem to have the integrations I wanted so didn’t try more at the time.
What I’d really like to find is some lsp servers that can cope with really large repos. That has been one of the limits I’ve pushed up against. Maybe I should try building something that can handle doing most of the source indexing in one shot and then just use that for a bunch of lsp style operations, but I worry this is just me trying to boil a different ocean.
I moved from lsp-mode to eglot 4 days ago, for Python, I'm happy.
I published a minimal view of my current configuration here: https://discuss.afpy.org/t/configuration-emacs-minimale-en-2026/3001
I switched from elpy to eglot + basedpyright nearly a year ago and I'm also quite happy.
Although I'm finding some completion annoyances; like foo<tab><tab> might automatically import some bizarre thing from basedpyright when I have some symbols in scope that also match it, and I haven't managed to just complete longest matching string, but otherwise it's pretty nice.
I envy folks that can get Emacs to work as a modern IDE. I use Emacs keybindings, but for the life of me I cannot get Emacs to work as an IDE within 6 hours-8hours of time spent.
I had tried to get it to work with FB Flow (I used it instead of typescript for a time) across Linux and FreeBSD dev environment -- and gave up.
Just last weekend I tried to get it to work as full-features Python environment with treesitter on Windows, gave up.
Too much to configure , too much extra downloads of dlls (tree-sitter parsers), just too much of everything to get to act as a competent IDE. I no longer want to invest time in that thing.. .but once in a while it is nice to just type emacs -nw in any terminal and have a familiar editing platform...
For Python, you can try this:
(fido-vertical-mode) ; nice completion for M-x
(which-key-mode) ; learn keyboard shortcuts
;; completion:
;; C-M-i or TAB: accept suggestion if unique/shown, pop up completion list otherwise
;; M-i; complete as much as possible / pop up completion list
(global-completion-preview-mode 1) ; show things that you can tab-complete
(setq tab-always-indent 'complete) ; allow tab to complete
(setq text-mode-ispell-word-completion nil) ; but do not complete dictionary words
(setq completion-auto-select t) ; focus the minibuffer for completion
(setq completions-format 'one-column)
;; for eglot snippet completion
(use-package yasnippet :ensure t)
(yas-global-mode 1)
;; pipx install basedpyright
(add-hook 'python-mode-hook 'eglot-ensure)
;; The default setting is higher than strict, and complains (amongst others) about missing type annotations
(setq-default eglot-workspace-configuration
'(:basedpyright (:typeCheckingMode "strict")
:basedpyright.analysis (:diagnosticSeverityOverrides (
:reportMissingParameterType "none"
:reportUnknownParameterType "none"
:reportUnknownVariableType "none"
:reportUnknownMemberType "none"
:reportUnknownArgumentType "none"))))
Just make sure that basedpyright is in your path and you should be good to go- just tested and that gets me proper completion and syntax highlighting (no tree-sitter grammar needed).
That's just a bare minimum trim from my full config.
Note that although eglot and lsp-mode are the two more popular LSP clients for emacs, there are other ones like lspce and lsp-bridge.
I've been a happy eglot user for years. But unfortunately it does have a design flaw that I think it will become harder. It assumes that there should be one client per buffer. Which is a fair assumption to make at the time when eglot was written. But increasingly it is more and more common to want to run multiple LSP servers in one buffer. The current recommendation is to use another program to act as an LSP muxer.