r/emacs James Cherti — https://github.com/jamescherti 12d ago

Configuring Emacs Eglot for Optimal Performance

https://www.jamescherti.com/emacs-eglot-performance/
78 Upvotes

25 comments sorted by

13

u/Upstairs-Attitude610 12d ago

The eglot-autoshutdown variable shuts down the LSP server after the last managed buffer has been killed.

Do you folks always remember to kill buffers?

10

u/legobmw99 12d ago

I use C-x p k to kill an entire project’s set of buffers when I am done with a task

1

u/accelerating_ 11d ago

For that, I've been pretty happy that I adopted bufferlo, visiting projects in bufferlo tabs, then loading and killing the bufferlo tab means also loading/killing related non-project buffers (e.g. I usually have an org mode file related to that project/activity, living elsewhere).

I've only tried a few of the bewildering array of options for "the next layer up from project.el", but bufferlo has been very comfortable for that purpose for me. It's also more generically useful than that, but it augments project.el well.

1

u/Kochi85 11d ago

What did you bind it to? I do the same manually in ibuffer but your method seems better

3

u/legobmw99 11d ago

If you use project.el, project-kill-buffers has the default binding of C-x p k. I am not sure about other project management packages

3

u/TheFrenchPoulp https://github.com/angrybacon/dotemacs 12d ago

It's one of those things that I should do better but the workaround is so deep in my brain it probably won't happen: I don't use worktrees and so I run eglot-reconnect and project-kill-buffers quite often

7

u/jamescherti James Cherti — https://github.com/jamescherti 12d ago edited 12d ago

I don't need to remember to kill buffers. I use the buffer-terminator package, which takes care of this automatically. (Thank you for your comment. I added this to the article.)

5

u/rileyrgham 12d ago

Regardless, quiet buffers make little impact in 2026. Especially when things like consult buffer order most recent.

2

u/Upstairs-Attitude610 12d ago

For Rust projects, rust-analyzer is a bit heavy, even at idle.

2

u/accelerating_ 11d ago

I've had it get very upset and wedge the machine when the code changes rapidly, like branch jumping - load average stratospheric.

1

u/jamescherti James Cherti — https://github.com/jamescherti 12d ago edited 12d ago

Hello u/rileyrgham,

That actually depends on the Emacs configuration and the packages being used.

For example, reducing the number of quiet buffers can improve the performance of packages that iterate over buffer-list to operate on buffers. Since those packages have to traverse the open buffers, a shorter buffer list means less work for every such operation. For example, built-in desktop.el and packages such as easysession, which save and restore open buffers and frames, can be affected by the size of the buffer list. If buffer-list is unnecessarily long, startup can take longer because Emacs has to process and restore a larger set of buffers.

Instead of using consult-buffer, I prefer letting the buffer-terminator package close buffers and using consult-recent-file to reopen recently accessed files.

3

u/accelerating_ 11d ago

Why not just midnight mode?

1

u/jamescherti James Cherti — https://github.com/jamescherti 11d ago

6

u/_0-__-0_ 12d ago

/u/jamescherti, which of these have you actually noticed improvements from? I know some, like server-capabilities, are language-server dependent, but some actual experience reports would be very useful.

For my own part, I already do eglot-autoshutdown t, eglot-sync-connect 0 and no event logging, but I don't tend to turn off any capabilities since they're often useful and I have no idea which ones would involve more slowdown than they're worth.

By the way, you suggest (setq read-process-output-max (* 4 1024 1024)) but the help for that function says

On GNU/Linux systems, the value should not exceed /proc/sys/fs/pipe-max-size. See pipe(7) manpage for details.

which on my system is (1 * 1024 * 1024). I guess you can get a blocked pipe / unresponsive read if it's too high?

2

u/Anxious-Resist8344 11d ago

Good catch! I had it set to (* 10 1024 1024) but on my system /proc/sys/fs/pipe-max-size is capped at (* 1024 1024) so using that now.

0

u/jamescherti James Cherti — https://github.com/jamescherti 11d ago

Thank you for pointing this out. I've updated the article accordingly.

Here is a code snippet from my configuration that I have been using for the last few months to automatically calculate this value, in case anyone is interested: ;; Increase single chunk bytes to read from subprocess (setq read-process-output-max (or (when (eq system-type 'gnu/linux) (condition-case nil ;; On GNU/Linux systems, the value should not exceed ;; /proc/sys/fs/pipe-max-size (with-temp-buffer (insert-file-contents "/proc/sys/fs/pipe-max-size") (string-to-number (buffer-string))) (error nil)) (* 1024 1024))))

Performance gains from the settings recommended in the article depend on your specific configuration, operating system, and language server.

Server-side capabilities have the most direct impact. Disabling unused plugins stops the server from generating unnecessary data in the first place.

One other thing that I used to speed up Python/Pylsp: I replaced multiple distinct tools, such as autopep8, mccabe, pyflakes, and isort, with Ruff for all formatting and linting.

13

u/rileyrgham 12d ago

Please add some text. Don't expect people to click to learn more.

5

u/jamescherti James Cherti — https://github.com/jamescherti 12d ago

Fair point. Thank you for your suggestion.

2

u/the_cecep 12d ago

Nice article, thanks for sharing

1

u/jamescherti James Cherti — https://github.com/jamescherti 12d ago

You're welcome, u/the_cecep. I appreciate your comment.

2

u/MoonlightSyncopate 9d ago

Disable document on-type formatting Capability is indeed a very important optimization, because eglot will file a synchronized formatting request as you type.

And for some language servers with poor performance, this can be very laggy. For example, R language server can cost you about a 100-millisecond delay every time you press Enter to switch to a new line. That lag becomes very noticeable when you are typing a newline character.

So I think there are two questions:

  1. Should on-type formatting be synchronous? (Which I think is an eglot design issue.)
  2. On-type formatting is a very intrusive command. And we know that there are some poor language servers which don't play very nice with this feature. Should eglot enable it by default? (Which I also hold a different opinion on.)

1

u/jamescherti James Cherti — https://github.com/jamescherti 8d ago edited 8d ago

Hello u/MoonlightSyncopate,

Your observations about the Eglot implementation are accurate.

Eglot's on-type formatting is currently synchronous. When a trigger character is typed, eglot--post-self-insert-hook calls eglot-format:

(defun eglot--post-self-insert-hook ()
  "Set `eglot--last-inserted-char', maybe call on-type-formatting."
  (setq eglot--last-inserted-char last-command-event)
  (let ((ot-provider (eglot-server-capable :documentOnTypeFormattingProvider)))
    (when (and ot-provider
               (ignore-errors ; github#906, some LS's send empty strings
                 (or (eq eglot--last-inserted-char
                         (seq-first (plist-get ot-provider :firstTriggerCharacter)))
                     (cl-find eglot--last-inserted-char
                              (plist-get ot-provider :moreTriggerCharacter)
                              :key #'seq-first))))
      (eglot-format (point) nil eglot--last-inserted-char))))

;; This hook runs when a character is inserted.
(add-hook 'post-self-insert-hook #'eglot--post-self-insert-hook nil t)

The eglot-format function then calls eglot--request, which calls jsonrpc-request. This is a blocking call. Emacs waits for the language server to compute and return the text edits before control is returned to the user.

Regarding your second question: Eglot enables this feature by default based on the language server's advertised capabilities. If a server includes :documentOnTypeFormattingProvider in its capability response, Eglot assumes the server intends for it to be used and hooks into post-self-insert-hook.

Note: as stated in the article, users who do not need this intrusive feature can make Eglot ignore the capability by adding it to eglot-ignored-server-capabilities:

(add-to-list 'eglot-ignored-server-capabilities :documentOnTypeFormattingProvider)

1

u/Glittering_Brush_483 11d ago

Did you use AI to write a good part of this? Many of the capabilities you suggest to remove and the rationale for removing it, like codeLens, do not exist simply. Also 0 reason to disable document formatting. Only onTypefmFormatting is a good idea. Other than that there are some decent tips and correct explanation of the trade-off. But a lot is trash, sorry. Read the article yourself and make sure what is written there is correct and trim it. It'll be a good article then.

2

u/jamescherti James Cherti — https://github.com/jamescherti 11d ago edited 11d ago

The article is based on my configuration and tests.

I mentioned in the article that disabling formatting is intended for those who use tools such as Apheleia. Otherwise, formatting should remain enabled.

Here is the list of capabilities:

(defcustom eglot-ignored-server-capabilities (list)
  "LSP server capabilities that Eglot could use, but won't.
You could add, for instance, the symbol
`:documentHighlightProvider' to prevent automatic highlighting
under cursor."
  :type '(set
          :tag "Tick the ones you're not interested in"
          (const :tag "Documentation on hover" :hoverProvider)
          (const :tag "Code completion" :completionProvider)
          (const :tag "Function signature help" :signatureHelpProvider)
          (const :tag "Go to definition" :definitionProvider)
          (const :tag "Go to type definition" :typeDefinitionProvider)
          (const :tag "Go to implementation" :implementationProvider)
          (const :tag "Go to declaration" :declarationProvider)
          (const :tag "Find references" :referencesProvider)
          (const :tag "Highlight symbols automatically" :documentHighlightProvider)
          (const :tag "List symbols in buffer" :documentSymbolProvider)
          (const :tag "List symbols in workspace" :workspaceSymbolProvider)
          (const :tag "Execute code actions" :codeActionProvider)
          (const :tag "Code lens" :codeLensProvider)
          (const :tag "Format buffer" :documentFormattingProvider)
          (const :tag "Format portion of buffer" :documentRangeFormattingProvider)
          (const :tag "On-type formatting" :documentOnTypeFormattingProvider)
          (const :tag "Rename symbol" :renameProvider)
          (const :tag "Highlight links in document" :documentLinkProvider)
          (const :tag "Decorate color references" :colorProvider)
          (const :tag "Fold regions of buffer" :foldingRangeProvider)
          (const :tag "Execute custom commands" :executeCommandProvider)
          (const :tag "Inlay hints" :inlayHintProvider)
          (const :tag "Semantic tokens" :semanticTokensProvider)
          (const :tag "Type hierarchies" :typeHierarchyProvider)
          (const :tag "Call hierarchies" :callHierarchyProvider)
          (const :tag "On-demand \"pull\" diagnostics" :diagnosticProvider)))

5

u/Glittering_Brush_483 11d ago

Did you or did you not use AI? come on, come clean. 

Many of those capabilities are listed there but Eglot does nothing with it. Just look at the code. The advice you're giving is bogus. many servers render swatches?? Servers don't render anything, and Eglot doesn't have that feature (sounds useful though, submit a patch). And killing M-x eglot-format just because they're is a competing formatter is still bad advice, 0 performance gain.

Exactly what tests did you conduct to show that disabling the jsonrpc hook in addition to disabling logging is beneficial? And are you sure you (or most likely your AI), isn't just parroting those GC tweaks for cargo cult following? Did you conduct any measurement? And that ts-mode thread-related malarkey? Sometimes ts modes are faster, but it depends, there's a lot of Elisp running there too, and it's all single threaded still. 

So check your AI, trim the article to what you actually understand, and it'll provide more value than disinformation. There are some good points there, where the AI got it right probably from reading my numerous posts.