Replies: 1 comment 3 replies
-
zovt ***@***.***> writes:
Hello! I'm trying to use one typescript-language-server process to manage multiple buffers. Some are javascript-mode,
some are typescript-mode. The problem is that, as far as I can tell, the language-id slot of the server class is set at
first connection (e.g. if I open a file in javascript-mode first, the server language-id is javascript for the lifetime
of the server). It would be really useful if this value could be determined based on the current buffer's major mode
instead, as this would allow me to se a singular typescript-language-server process to handle all of my buffers. As it
stands, I need to have 2 servers running, one to handle javascript buffers and one to handle typescript buffers.
Let me know if this makes sense! Happy to elaborate more :) or if this is something that already exists do let me know!
Thanks in advance
Wow, what a boring problem you've unearthed! Yes, I think you say makes
full sense. The complex languageId modelling happened in the past to
appease the majority of servers is showing its mistakes, as more
examples of "language server" become "server for languages plural" and
start to actually care about what is sent in languageId.
So, i've come up with this patch which redoes all this. Can you test
it?
```diff
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 3f00281e155..c4f773c8426 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -844,12 +844,9 @@ eglot-lsp-server
:documentation "Short nickname for the associated project."
:accessor eglot--project-nickname
:reader eglot-project-nickname)
- (major-modes
- :documentation "Major modes server is responsible for in a given project."
- :accessor eglot--major-modes)
- (language-id
- :documentation "Language ID string for the mode."
- :accessor eglot--language-id)
+ (languages
+ :documentation "Alist ((MODE . LANGUAGE-ID-STRING)...) of managed languages."
+ :accessor eglot--languages)
(capabilities
:documentation "JSON object containing server capabilities."
:accessor eglot--capabilities)
@@ -884,6 +881,12 @@ eglot-lsp-server
:documentation
"Represents a server. Wraps a process for LSP communication.")
+(defun eglot--major-modes (s) "Major modes server S is responsible for."
+ (mapcar #'car (eglot--languages s)))
+
+(defun eglot--language-ids (s) "LSP Language ID strings for server S's modes."
+ (mapcar #'cdr (eglot--languages s)))
+
(cl-defmethod initialize-instance :before ((_server eglot-lsp-server) &optional args)
(cl-remf args :initializationOptions))
@@ -969,42 +972,44 @@ eglot-command-history
(defun eglot--lookup-mode (mode)
"Lookup `eglot-server-programs' for MODE.
-Return (MANAGED-MODES LANGUAGE-ID CONTACT-PROXY).
+Return (LANGUAGES . CONTACT-PROXY).
MANAGED-MODES is a list with MODE as its first element.
Subsequent elements are other major modes also potentially
managed by the server that is to manage MODE.
…-If not specified in `eglot-server-programs' (which see),
-LANGUAGE-ID is determined from MODE's name.
+LANGUAGE-IDS is a list of the same length as MANAGED-MODES. Each
+elem is derived from the corresponding mode name, if not
+specified in `eglot-server-programs' (which see).
CONTACT-PROXY is the value of the corresponding
`eglot-server-programs' entry."
- (cl-loop
- for (modes . contact) in eglot-server-programs
- for mode-symbols = (cons mode
- (delete mode
- (mapcar #'car
- (mapcar #'eglot--ensure-list
- (eglot--ensure-list modes)))))
- thereis (cl-some
- (lambda (spec)
- (cl-destructuring-bind (probe &key language-id &allow-other-keys)
- (eglot--ensure-list spec)
- (and (provided-mode-derived-p mode probe)
- (list
- mode-symbols
- (or language-id
- (or (get mode 'eglot-language-id)
- (get spec 'eglot-language-id)
- (string-remove-suffix "-mode" (symbol-name mode))))
- contact))))
- (if (or (symbolp modes) (keywordp (cadr modes)))
- (list modes) modes))))
+ (cl-flet ((languages (main-mode-sym specs)
+ (let* ((res
+ (mapcar (jsonrpc-lambda (sym &key language-id &allow-other-keys)
+ (cons sym
+ (or language-id
+ (or (get sym 'eglot-language-id)
+ (replace-regexp-in-string
+ "\\(?:-ts\\)?-mode$" ""
+ (symbol-name sym))))))
+ specs))
+ (head (cl-find main-mode-sym res :key #'car)))
+ (cons head (delq head res)))))
+ (cl-loop
+ for (modes . contact) in eglot-server-programs
+ for specs = (mapcar #'eglot--ensure-list
+ (if (or (symbolp modes) (keywordp (cadr modes)))
+ (list modes) modes))
+ thereis (cl-some (lambda (spec)
+ (cl-destructuring-bind (sym &key &allow-other-keys) spec
+ (and (provided-mode-derived-p mode sym)
+ (cons (languages sym specs) contact))))
+ specs))))
(defun eglot--guess-contact (&optional interactive)
"Helper for `eglot'.
-Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID). If INTERACTIVE is
+Return (MANAGED-MODES PROJECT CLASS CONTACT LANG-IDS). If INTERACTIVE is
non-nil, maybe prompt user, else error as soon as something can't
be guessed."
(let* ((guessed-mode (if buffer-file-name major-mode))
@@ -1022,11 +1027,10 @@ eglot--guess-contact
((not guessed-mode)
(eglot--error "Can't guess mode to manage for `%s'" (current-buffer)))
(t guessed-mode)))
- (triplet (eglot--lookup-mode main-mode))
- (managed-modes (car triplet))
- (language-id (or (cadr triplet)
- (string-remove-suffix "-mode" (symbol-name guessed-mode))))
- (guess (caddr triplet))
+ (languages-and-contact (eglot--lookup-mode main-mode))
+ (managed-modes (mapcar #'car (car languages-and-contact)))
+ (language-ids (mapcar #'cdr (car languages-and-contact)))
+ (guess (cdr languages-and-contact))
(guess (if (functionp guess)
(funcall guess interactive)
guess))
@@ -1074,7 +1078,7 @@ eglot--guess-contact
full-program-invocation
'eglot-command-history)))
guess)))
- (list managed-modes (eglot--current-project) class contact language-id)))
+ (list managed-modes (eglot--current-project) class contact language-ids)))
(defvar eglot-lsp-context)
(put 'eglot-lsp-context 'variable-documentation
@@ -1092,24 +1096,25 @@ eglot--current-project
`(transient . ,(expand-file-name default-directory)))))
;;;###autoload
-(defun eglot (managed-major-mode project class contact language-id
+(defun eglot (managed-major-modes project class contact language-ids
&optional _interactive)
- "Start LSP server in support of PROJECT's buffers under MANAGED-MAJOR-MODE.
+ "Start LSP server for PROJECT's buffers under MANAGED-MAJOR-MODES.
-This starts a Language Server Protocol (LSP) server suitable for the
-buffers of PROJECT whose `major-mode' is MANAGED-MAJOR-MODE.
-CLASS is the class of the LSP server to start and CONTACT specifies
-how to connect to the server.
+This starts a Language Server Protocol (LSP) server suitable for
+the buffers of PROJECT whose `major-mode' is among
+MANAGED-MAJOR-MODES. CLASS is the class of the LSP server to
+start and CONTACT specifies how to connect to the server.
-Interactively, the command attempts to guess MANAGED-MAJOR-MODE
-from the current buffer's `major-mode', CLASS and CONTACT from
-`eglot-server-programs' looked up by the major mode, and PROJECT from
-`project-find-functions'. The search for active projects in this
-context binds `eglot-lsp-context' (which see).
+Interactively, the command attempts to guess MANAGED-MAJOR-MODES,
+CLASS, CONTACT, and LANGUAGE-IDS from `eglot-server-programs',
+according to the current buffer's `major-mode'. PROJECT is
+guessed from `project-find-functions'. The search for active
+projects in this context binds `eglot-lsp-context' (which see).
-If it can't guess, it prompts the user for the mode and the server.
-With a single \\[universal-argument] prefix arg, it always prompts for COMMAND.
-With two \\[universal-argument], it also always prompts for MANAGED-MAJOR-MODE.
+If it can't guess, it prompts the user for the mode and the
+server. With a single \\[universal-argument] prefix arg, it
+always prompts for COMMAND. With two \\[universal-argument], it
+also always prompts for MANAGED-MAJOR-MODE.
The LSP server of CLASS is started (or contacted) via CONTACT.
If this operation is successful, current *and future* file
@@ -1127,8 +1132,8 @@ eglot
keyword-value plist used to initialize CLASS or a plain list as
described in `eglot-server-programs', which see.
-LANGUAGE-ID is the language ID string to send to the server for
-MANAGED-MAJOR-MODE, which matters to a minority of servers.
+LANGUAGE-IDS is a list of language ID string to send to the
+server for each element in MANAGED-MAJOR-MODES.
INTERACTIVE is ignored and provided for backward compatibility."
(interactive
@@ -1139,8 +1144,9 @@ eglot
(user-error "[eglot] Connection attempt aborted by user."))
(prog1 (append (eglot--guess-contact t) '(t))
(when current-server (ignore-errors (eglot-shutdown current-server))))))
- (eglot--connect (eglot--ensure-list managed-major-mode)
- project class contact language-id))
+ (eglot--connect (eglot--ensure-list managed-major-modes)
+ project class contact
+ (eglot--ensure-list language-ids)))
(defun eglot-reconnect (server &optional interactive)
"Reconnect to SERVER.
@@ -1152,7 +1158,7 @@ eglot-reconnect
(eglot--project server)
(eieio-object-class-name server)
(eglot--saved-initargs server)
- (eglot--language-id server))
+ (eglot--language-ids server))
(eglot--message "Reconnected!"))
(defvar eglot--managed-mode) ; forward decl
@@ -1225,8 +1231,8 @@ eglot--cmd
(defvar-local eglot--cached-server nil
"A cached reference to the current Eglot server.")
-(defun eglot--connect (managed-modes project class contact language-id)
- "Connect to MANAGED-MODES, LANGUAGE-ID, PROJECT, CLASS and CONTACT.
+(defun eglot--connect (managed-modes project class contact language-ids)
+ "Connect to MANAGED-MODES, LANGUAGE-IDS, PROJECT, CLASS and CONTACT.
This docstring appeases checkdoc, that's all."
(let* ((default-directory (project-root project))
(nickname (project-name project))
@@ -1299,8 +1305,9 @@ eglot--connect
(setf (eglot--saved-initargs server) initargs)
(setf (eglot--project server) project)
(setf (eglot--project-nickname server) nickname)
- (setf (eglot--major-modes server) (eglot--ensure-list managed-modes))
- (setf (eglot--language-id server) language-id)
+ (setf (eglot--languages server)
+ (cl-loop for m in managed-modes for l in language-ids
+ collect (cons m l)))
(setf (eglot--inferior-process server) autostart-inferior-process)
(run-hook-with-args 'eglot-server-initialized-hook server)
;; Now start the handshake. To honor `eglot-sync-connect'
@@ -2354,7 +2361,7 @@ eglot--TextDocumentItem
(append
(eglot--VersionedTextDocumentIdentifier)
(list :languageId
- (eglot--language-id (eglot--current-server-or-lose))
+ (alist-get major-mode (eglot--languages (eglot--current-server-or-lose)))
:text
(eglot--widening
(buffer-substring-no-properties (point-min) (point-max))))))
```
|
Beta Was this translation helpful? Give feedback.
3 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Hello! I'm trying to use one
typescript-language-server
process to manage multiple buffers. Some arejavascript-mode
, some aretypescript-mode
. The problem is that, as far as I can tell, thelanguage-id
slot of the server class is set at first connection (e.g. if I open a file injavascript-mode
first, the serverlanguage-id
isjavascript
for the lifetime of the server). It would be really useful if this value could be determined based on the current buffer's major mode instead, as this would allow me to se a singulartypescript-language-server
process to handle all of my buffers. As it stands, I need to have 2 servers running, one to handlejavascript
buffers and one to handletypescript
buffers.Let me know if this makes sense! Happy to elaborate more :) or if this is something that already exists do let me know! Thanks in advance
Beta Was this translation helpful? Give feedback.
All reactions