diff options
| author | João Távora | 2026-01-09 11:09:21 +0000 |
|---|---|---|
| committer | João Távora | 2026-01-11 03:42:01 +0000 |
| commit | 6921244718522b27461b06cca7b29e187861f46f (patch) | |
| tree | a61fdc9a38f0be0c5882814c99ff95f2b4530065 | |
| parent | cc5ebad8410a0523509ac0e8d23eb96ab43bef0a (diff) | |
| download | emacs-6921244718522b27461b06cca7b29e187861f46f.tar.gz emacs-6921244718522b27461b06cca7b29e187861f46f.zip | |
Eglot: document LSP server multiplexer support
This documents how to use LSP multiplexer programs like Rassumfrassum
to connect multiple language servers to a single buffer.
* doc/misc/eglot.texi (Top): Add "Multi-server support" menu entry.
(Multi-server support): New chapter.
(Using Rassumfrassum, Design rationale): New sections documenting
how to use the Rassumfrassum multiplexer program with Eglot, with
practical examples for C++, Python, and multi-language files.
(Performance): Mention Rassumfrassum as solution for JSONRPC traffic
performance issues.
(Reporting bugs): Add guidance for troubleshooting multiplexer-related
bugs. Improve project description guidance. Fix various typos.
* lisp/progmodes/eglot.el (eglot-server-programs): Add a couple
of rass entries.
* etc/EGLOT-NEWS: Announce support for LSP server multiplexers via
Rassumfrassum.
| -rw-r--r-- | doc/misc/eglot.texi | 181 | ||||
| -rw-r--r-- | etc/EGLOT-NEWS | 14 | ||||
| -rw-r--r-- | lisp/progmodes/eglot.el | 9 |
3 files changed, 190 insertions, 14 deletions
diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi index 532416f17ad..579c568f264 100644 --- a/doc/misc/eglot.texi +++ b/doc/misc/eglot.texi | |||
| @@ -99,6 +99,7 @@ read this manual from within Emacs, type @kbd{M-x eglot-manual | |||
| 99 | * Using Eglot:: Important Eglot commands and variables. | 99 | * Using Eglot:: Important Eglot commands and variables. |
| 100 | * Customizing Eglot:: Eglot customization and advanced features. | 100 | * Customizing Eglot:: Eglot customization and advanced features. |
| 101 | * Advanced server configuration:: Fine-tune a specific language server | 101 | * Advanced server configuration:: Fine-tune a specific language server |
| 102 | * Multi-server support:: Use more than one server in a buffer | ||
| 102 | * Extending Eglot:: Writing Eglot extensions in Elisp | 103 | * Extending Eglot:: Writing Eglot extensions in Elisp |
| 103 | * Troubleshooting Eglot:: Troubleshooting and reporting bugs. | 104 | * Troubleshooting Eglot:: Troubleshooting and reporting bugs. |
| 104 | * GNU Free Documentation License:: The license for this manual. | 105 | * GNU Free Documentation License:: The license for this manual. |
| @@ -1511,6 +1512,152 @@ is serialized by Eglot to the following JSON text: | |||
| 1511 | @} | 1512 | @} |
| 1512 | @end example | 1513 | @end example |
| 1513 | 1514 | ||
| 1515 | @node Multi-server support | ||
| 1516 | @chapter Multi-server support | ||
| 1517 | @cindex multiple servers per buffer | ||
| 1518 | @cindex LSP server multiplexer | ||
| 1519 | @cindex per-buffer multiple servers | ||
| 1520 | |||
| 1521 | One of the most frequently requested features for Eglot in close to a | ||
| 1522 | decade of existence is the ability to use more than one LSP server in a | ||
| 1523 | single buffer. This is distinct from using multiple servers in a | ||
| 1524 | project, where each server manages a disjoint set of files written in | ||
| 1525 | different languages. | ||
| 1526 | |||
| 1527 | The latter case---multiple servers for different files---is | ||
| 1528 | intrinsically supported by Eglot. For example, in a web project with | ||
| 1529 | JavaScript, CSS, and Python files, Eglot can seamlessly manage separate | ||
| 1530 | language servers for each file type within the same project | ||
| 1531 | (@pxref{Starting Eglot}). Each buffer communicates with its appropriate | ||
| 1532 | server, and this works out-of-the-box. | ||
| 1533 | |||
| 1534 | However, there are several scenarios where multiple servers per buffer | ||
| 1535 | are useful: | ||
| 1536 | |||
| 1537 | @itemize @bullet | ||
| 1538 | @item | ||
| 1539 | Combining a spell-checking language server like @command{codebook-lsp} | ||
| 1540 | with language-specific servers for C++, Go, or Python files. The | ||
| 1541 | spell-checker provides diagnostics for comments and strings, while the | ||
| 1542 | language server handles syntax and semantics. | ||
| 1543 | |||
| 1544 | @item | ||
| 1545 | One might want multiple servers to cover different aspects of the same | ||
| 1546 | language. For Python, you might combine @command{ty} for type checking | ||
| 1547 | with @command{ruff} for linting and formatting. For JavaScript, you | ||
| 1548 | might use @command{typescript-language-server} for language features | ||
| 1549 | together with @command{eslint} for linting. | ||
| 1550 | |||
| 1551 | @item | ||
| 1552 | When working on multi-language files like Vue @file{.vue} files, which | ||
| 1553 | contain JavaScript, CSS, and HTML embedded in a single file, multiple | ||
| 1554 | servers can manage the different areas of the buffer. | ||
| 1555 | @end itemize | ||
| 1556 | |||
| 1557 | These use cases are not directly supported by Eglot's architecture, | ||
| 1558 | however, you can use a language-agnostic @dfn{LSP server multiplexer} | ||
| 1559 | that sits between Eglot and the actual language servers. Eglot still | ||
| 1560 | communicates with a single LSP server process in each buffer, but that | ||
| 1561 | process mediates communication to multiple language-specific servers, | ||
| 1562 | meaning that for practical purposes, it's @emph{as if} Eglot was | ||
| 1563 | connected to them directly. | ||
| 1564 | |||
| 1565 | This approach is more powerful and user-friendly than current | ||
| 1566 | workarounds that combine one LSP server in a buffer with additional | ||
| 1567 | non-LSP mechanisms such as extra Flymake backends (@pxref{Top,,, | ||
| 1568 | Flymake, GNU Flymake manual}) for the same buffer. | ||
| 1569 | |||
| 1570 | @menu | ||
| 1571 | * Using Rassumfrassum:: Setup the @code{rass} LSP multiplexer | ||
| 1572 | * Design rationale:: Benefits and drawbacks of LSP multiplexers | ||
| 1573 | @end menu | ||
| 1574 | |||
| 1575 | @node Using Rassumfrassum | ||
| 1576 | @section Using Rassumfrassum | ||
| 1577 | |||
| 1578 | @uref{https://github.com/joaotavora/rassumfrassum, Rassumfrassum} is an | ||
| 1579 | LSP server multiplexer program that fits the bill. Like most language | ||
| 1580 | servers, it must be installed separately since it is not bundled with | ||
| 1581 | Emacs (at time of writing). The installation is similar to installing | ||
| 1582 | any other language server, and usually amounts to making sure the | ||
| 1583 | program executable is somewhere in @code{PATH} or @code{exec-path}. | ||
| 1584 | |||
| 1585 | The Rassumfrassum program, invoked via the @command{rass} command, works | ||
| 1586 | by spawning multiple LSP server subprocesses and aggregating their | ||
| 1587 | capabilities, requests, and responses into a single unified LSP | ||
| 1588 | interface. From Eglot's perspective, it appears to be communicating with | ||
| 1589 | a single server. | ||
| 1590 | |||
| 1591 | To use Rassumfrassum with Eglot, you can start it interactively with a | ||
| 1592 | prefix argument to @code{eglot} and specify the @command{rass} command | ||
| 1593 | followed by the actual servers you want to use, separated by @code{--}: | ||
| 1594 | |||
| 1595 | @example | ||
| 1596 | C-u M-x eglot RET rass -- clangd -- codebook-lsp serve RET | ||
| 1597 | @end example | ||
| 1598 | |||
| 1599 | @noindent | ||
| 1600 | This starts @command{clangd} for C++ language support and | ||
| 1601 | @command{codebook-lsp} for spell-checking in the same buffer. | ||
| 1602 | |||
| 1603 | For Python, you might use: | ||
| 1604 | |||
| 1605 | @example | ||
| 1606 | C-u M-x eglot RET rass -- ty server -- ruff server RET | ||
| 1607 | @end example | ||
| 1608 | |||
| 1609 | @noindent | ||
| 1610 | or simply @kbd{C-u M-x eglot RET rass python}, using the ``preset'' | ||
| 1611 | feature. This combines @command{ty} for type checking with | ||
| 1612 | @command{ruff} for linting and formatting. | ||
| 1613 | |||
| 1614 | These configurations can be integrated into the | ||
| 1615 | @code{eglot-server-programs} variable (@pxref{Setting Up LSP Servers}) | ||
| 1616 | for automatic use: | ||
| 1617 | |||
| 1618 | @lisp | ||
| 1619 | (with-eval-after-load 'eglot | ||
| 1620 | (add-to-list 'eglot-server-programs | ||
| 1621 | '(c-ts-base-mode . ("rass" "--" "clangd" "--" | ||
| 1622 | "codebook-lsp" "serve"))) | ||
| 1623 | (add-to-list 'eglot-server-programs | ||
| 1624 | '(python-mode . ("rass" "--" "ty" "server" "--" | ||
| 1625 | "ruff" "server")))) | ||
| 1626 | @end lisp | ||
| 1627 | |||
| 1628 | @node Design rationale | ||
| 1629 | @section Design rationale | ||
| 1630 | |||
| 1631 | Using an LSP server multiplexer like @command{rass} relieves Eglot from | ||
| 1632 | knowing about the specific characteristics of individual servers and the | ||
| 1633 | complexity of managing multiple simultaneous server connections per | ||
| 1634 | buffer. This helps preserve the essential features that distinguish | ||
| 1635 | Eglot's code base from other LSP offers for Emacs: simple, performant | ||
| 1636 | and mindful of the core tenet of LSP, which is for a client to be | ||
| 1637 | language-agnostic. | ||
| 1638 | |||
| 1639 | This approach has an additional benefit: because the multiplexer | ||
| 1640 | mediates all communication between Eglot and the servers, it can take | ||
| 1641 | advantage of different optimization opportunities. For instance, at the | ||
| 1642 | system level it may be multi-threaded to process different JSONRPC | ||
| 1643 | streams in with true parallelism, something which is currently | ||
| 1644 | impossible to do in plain Elisp. At the LSP-level it can merge server | ||
| 1645 | responses intelligently, truncate unnecessarily large objects, and cache | ||
| 1646 | significant amounts of information in efficient ways. In many cases, | ||
| 1647 | this can reduce the amount of JSONRPC traffic exchanged with Emacs to | ||
| 1648 | levels well below what would occur if a client connected to multiple | ||
| 1649 | servers separately. Some of these optimizations may apply even when a | ||
| 1650 | program like @command{rass} is mediating communication to a single | ||
| 1651 | server. | ||
| 1652 | |||
| 1653 | The multiplexer approach is not without drawbacks. Since LSP is a | ||
| 1654 | relatively large protocol with a decade of existence and many backward | ||
| 1655 | compatibility concerns, combining the responses of servers using completely | ||
| 1656 | different mechanisms of the protocol to respond to the same request | ||
| 1657 | sometimes leads to complexity in covering the corner cases. However, | ||
| 1658 | offloading this complexity to a completely separate layer has proven | ||
| 1659 | very effective in practice. | ||
| 1660 | |||
| 1514 | @node Extending Eglot | 1661 | @node Extending Eglot |
| 1515 | @chapter Extending Eglot | 1662 | @chapter Extending Eglot |
| 1516 | 1663 | ||
| @@ -1687,9 +1834,13 @@ slowly, try to customize the variable @code{eglot-events-buffer-config} | |||
| 1687 | 0. This will disable recording any events and may speed things up. | 1834 | 0. This will disable recording any events and may speed things up. |
| 1688 | 1835 | ||
| 1689 | In other situations, the cause of poor performance lies in the language | 1836 | In other situations, the cause of poor performance lies in the language |
| 1690 | server itself. Servers use aggressive caching and other techniques to | 1837 | server itself. Some servers use aggressive caching and other techniques |
| 1691 | improve their performance. Often, this can be tweaked by changing the | 1838 | to improve their performance. Often, this can be tweaked by changing |
| 1692 | server configuration (@pxref{Advanced server configuration}). | 1839 | the server configuration (@pxref{Advanced server configuration}). |
| 1840 | |||
| 1841 | Another aspect that may cause performance degradation is the amount of | ||
| 1842 | JSONRPC information exchanged with Emacs. Using an LSP program like | ||
| 1843 | @ref{Using Rassumfrassum,Rassumfrassum} may alleviate such problems. | ||
| 1693 | 1844 | ||
| 1694 | @node Getting the latest version | 1845 | @node Getting the latest version |
| 1695 | @section Getting the latest version | 1846 | @section Getting the latest version |
| @@ -1751,10 +1902,17 @@ may be using. If possible, try to replicate the problem with the | |||
| 1751 | C/C@t{++} or Python servers, as these are very easy to install. | 1902 | C/C@t{++} or Python servers, as these are very easy to install. |
| 1752 | 1903 | ||
| 1753 | @item | 1904 | @item |
| 1754 | Describe how to setup a @emph{minimal} project directory where Eglot | 1905 | If using an LSP multiplexer server like @ref{Using Rassumfrassum, |
| 1906 | Rassumfrassum}, first verify if the program replicates by using one of | ||
| 1907 | the multiplexed servers directly. If it doesn't the problem lies in the | ||
| 1908 | LSP multiplexer program and should be reported there. | ||
| 1909 | |||
| 1910 | @item | ||
| 1911 | Include a description of a @emph{minimal} project directory where Eglot | ||
| 1755 | should be started for the problem to happen. Describe each file's name | 1912 | should be started for the problem to happen. Describe each file's name |
| 1756 | and its contents. Alternatively, you can supply the address of a public | 1913 | and its contents, or---sometimes better--- zip that project directory |
| 1757 | Git repository. | 1914 | completely and attach it. Alternatively, you can supply the address of |
| 1915 | a public Git repository. | ||
| 1758 | 1916 | ||
| 1759 | @item | 1917 | @item |
| 1760 | Include versions of the software used. The Emacs version can be | 1918 | Include versions of the software used. The Emacs version can be |
| @@ -1767,12 +1925,13 @@ first check if the problem isn't already fixed in the latest version | |||
| 1767 | It's also essential to include the version of ELPA packages that are | 1925 | It's also essential to include the version of ELPA packages that are |
| 1768 | explicitly or implicitly loaded. The optional but popular Company or | 1926 | explicitly or implicitly loaded. The optional but popular Company or |
| 1769 | Markdown packages are distributed as GNU ELPA packages, not to mention | 1927 | Markdown packages are distributed as GNU ELPA packages, not to mention |
| 1770 | Eglot itself in some situations. Some major modes (Go, Rust, etc.) are | 1928 | Eglot itself in some situations. Prefer reproducing the problem with |
| 1771 | provided by ELPA packages. It's sometimes easy to miss these, since | 1929 | built-in Treesit major modes like @code{go-ts-mode} or |
| 1772 | they are usually implicitly loaded when visiting a file in that | 1930 | @code{rust-ts-mode} since the non-ts modes for such languages are |
| 1773 | language. | 1931 | usually provided by ELPA packages, and it's often easy to miss them. |
| 1774 | 1932 | ||
| 1775 | ELPA packages usually live in @code{~/.emacs.d/elpa} (or what is in | 1933 | If you can't reproduce your bug without ELPA packages, you may find the |
| 1934 | ones you're using in @code{~/.emacs.d/elpa} (or what is in | ||
| 1776 | @code{package-user-dir}). Including a listing of files in that | 1935 | @code{package-user-dir}). Including a listing of files in that |
| 1777 | directory is a way to tell the maintainers about ELPA package versions. | 1936 | directory is a way to tell the maintainers about ELPA package versions. |
| 1778 | 1937 | ||
diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index 9c7786f09b9..8735e966ee9 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS | |||
| @@ -20,6 +20,20 @@ https://github.com/joaotavora/eglot/issues/1234. | |||
| 20 | 20 | ||
| 21 | * Changes to upcoming Eglot | 21 | * Changes to upcoming Eglot |
| 22 | 22 | ||
| 23 | ** Support for LSP server multiplexers via Rassumfrassum | ||
| 24 | |||
| 25 | Eglot can now leverage LSP server multiplexer programs like Rassumfrassum | ||
| 26 | (invoked via the 'rass' command) to use multiple language servers in a | ||
| 27 | single buffer. This enables combining spell-checkers with language | ||
| 28 | servers, using multiple servers for the same language (e.g., 'ty' for | ||
| 29 | type checking and 'ruff' for linting in Python), or handling | ||
| 30 | multi-language files like Vue. | ||
| 31 | |||
| 32 | Some invocations of 'rass' are offered as alternatives in the built-in | ||
| 33 | 'eglot-server-programs' variable. The manual (readable with 'M-x | ||
| 34 | eglot-manual') contains a comprehensive discussion of how to set up and | ||
| 35 | use multiplexers in the new "Multi-server support" chapter. | ||
| 36 | |||
| 23 | ** Support for pull diagnostics (github#1559, github#1290) | 37 | ** Support for pull diagnostics (github#1559, github#1290) |
| 24 | 38 | ||
| 25 | For servers supporting the 'diagnosticProvider' capability, Eglot | 39 | For servers supporting the 'diagnosticProvider' capability, Eglot |
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 8be3a459b95..b6c4d5b7a89 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el | |||
| @@ -238,7 +238,7 @@ automatically)." | |||
| 238 | (defvar eglot-server-programs | 238 | (defvar eglot-server-programs |
| 239 | ;; FIXME: Maybe this info should be distributed into the major modes | 239 | ;; FIXME: Maybe this info should be distributed into the major modes |
| 240 | ;; themselves where they could set a buffer-local `eglot-server-program' | 240 | ;; themselves where they could set a buffer-local `eglot-server-program' |
| 241 | ;; instead of keeping this database centralized. | 241 | ;; which would allow deprecating this database. |
| 242 | ;; FIXME: With `derived-mode-add-parents' in Emacs≥30, some of | 242 | ;; FIXME: With `derived-mode-add-parents' in Emacs≥30, some of |
| 243 | ;; those entries can be simplified, but we keep them for when | 243 | ;; those entries can be simplified, but we keep them for when |
| 244 | ;; `eglot.el' is installed via GNU ELPA in an older Emacs. | 244 | ;; `eglot.el' is installed via GNU ELPA in an older Emacs. |
| @@ -248,7 +248,8 @@ automatically)." | |||
| 248 | (vimrc-mode . ("vim-language-server" "--stdio")) | 248 | (vimrc-mode . ("vim-language-server" "--stdio")) |
| 249 | ((python-mode python-ts-mode) | 249 | ((python-mode python-ts-mode) |
| 250 | . ,(eglot-alternatives | 250 | . ,(eglot-alternatives |
| 251 | '("pylsp" "pyls" ("basedpyright-langserver" "--stdio") | 251 | '(("rass" "python") |
| 252 | "pylsp" "pyls" ("basedpyright-langserver" "--stdio") | ||
| 252 | ("pyright-langserver" "--stdio") | 253 | ("pyright-langserver" "--stdio") |
| 253 | ("pyrefly" "lsp") | 254 | ("pyrefly" "lsp") |
| 254 | ("ty" "server") | 255 | ("ty" "server") |
| @@ -262,7 +263,9 @@ automatically)." | |||
| 262 | (tsx-ts-mode :language-id "typescriptreact") | 263 | (tsx-ts-mode :language-id "typescriptreact") |
| 263 | (typescript-ts-mode :language-id "typescript") | 264 | (typescript-ts-mode :language-id "typescript") |
| 264 | (typescript-mode :language-id "typescript")) | 265 | (typescript-mode :language-id "typescript")) |
| 265 | . ("typescript-language-server" "--stdio")) | 266 | . ,(eglot-alternatives |
| 267 | '(("rass ts") | ||
| 268 | ("typescript-language-server" "--stdio")))) | ||
| 266 | ((bash-ts-mode sh-mode) . ("bash-language-server" "start")) | 269 | ((bash-ts-mode sh-mode) . ("bash-language-server" "start")) |
| 267 | ((php-mode phps-mode php-ts-mode) | 270 | ((php-mode phps-mode php-ts-mode) |
| 268 | . ,(eglot-alternatives | 271 | . ,(eglot-alternatives |