]> git.eshelyaron.com Git - emacs.git/commitdiff
Add support for "single server, multiple modes"
authorJoão Távora <joaotavora@gmail.com>
Mon, 26 Sep 2022 10:43:39 +0000 (11:43 +0100)
committerJoão Távora <joaotavora@gmail.com>
Mon, 26 Sep 2022 10:44:04 +0000 (11:44 +0100)
Previously, if an entry such as:

   ((c++-mode c-mode) . ("clangd)")

were found in eglot-server-programs, it meant that opening a .cpp file
and a .c file in the same project and enabling eglot for both would
lead to two clangd instances.  Now only one instance is created to
handle all buffers of those major modes, as long as they are in the
same project.

This change accomplishes this with minimal changes and NO modification
to the already complicated syntax of eglot-server-programs.

Naturally, this means that a subtle backward-incompatibility was
introduced.  If, instead of "clangd", someone is using some kind
"c++-or-c-but-not-both-at-once" server, this commit now breaks that
person's configuration.

After analysing the entries of this variable, an educated guess was
made that this situation is rare.  If it's not rare, then some change
to the syntax of eglot-server-programs will have to ensue.

* eglot.el (eglot-server-programs): Update docstring.
(eglot-lsp-server): Replace major-mode -> major-modes.
(eglot--lookup-mode): Rework.
(eglot--guess-contact): Rework.
(eglot--connect): Reword first parameter.
(eglot-reconnect): Use eglot--major-modes.
(eglot--read-server): Rework.
(eglot--ensure-list): New helper.
(eglot-current-server): Rework.
(eglot-handle-request workspace/configuration): Use first of
managed major modes.

* NEWS.md: Mention change.

GitHub-reference: per https://github.com/joaotavora/eglot/issues/681

lisp/progmodes/eglot.el

index fa29c60617930a63c7cf0002b343cf3cb52ed474..6a1eb1282c7219bd26ea36998ef99f7dfa97d948 100644 (file)
@@ -218,7 +218,8 @@ MAJOR-MODE can be:
 
 * A list combining the previous two alternatives, meaning
   multiple major modes will be associated with a single server
-  program.
+  program.  This association is such that the same resulting
+  server process will manage buffers of different major modes.
 
 CONTACT can be:
 
@@ -760,9 +761,9 @@ treated as in `eglot-dbind'."
     :documentation "Short nickname for the associated project."
     :accessor eglot--project-nickname
     :reader eglot-project-nickname)
-   (major-mode
-    :documentation "Major mode symbol."
-    :accessor eglot--major-mode)
+   (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)
@@ -879,16 +880,31 @@ PRESERVE-BUFFERS as in `eglot-shutdown', which see."
 
 (defun eglot--lookup-mode (mode)
   "Lookup `eglot-server-programs' for MODE.
-Return (LANGUAGE-ID . CONTACT-PROXY).  If not specified,
-LANGUAGE-ID is determined from MODE."
+Return (MANAGED-MODES LANGUAGE-ID CONTACT-PROXY).
+
+MANAGED-MODES is a list with MODE as its first elements.
+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.
+
+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)
-                  (if (consp spec) spec (list spec))
+                  (eglot--ensure-list spec)
                 (and (provided-mode-derived-p mode probe)
-                     (cons
+                     (list
+                      mode-symbols
                       (or language-id
                           (or (get mode 'eglot-language-id)
                               (get spec 'eglot-language-id)
@@ -903,7 +919,7 @@ Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID).  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))
-         (managed-mode
+         (main-mode
           (cond
            ((and interactive
                  (or (>= (prefix-numeric-value current-prefix-arg) 16)
@@ -916,10 +932,11 @@ be guessed."
            ((not guessed-mode)
             (eglot--error "Can't guess mode to manage for `%s'" (current-buffer)))
            (t guessed-mode)))
-         (lang-id-and-guess (eglot--lookup-mode guessed-mode))
-         (language-id (or (car lang-id-and-guess)
+         (triplet (eglot--lookup-mode main-mode))
+         (managed-modes (car triplet))
+         (language-id (or (cadr triplet)
                           (string-remove-suffix "-mode" (symbol-name guessed-mode))))
-         (guess (cdr lang-id-and-guess))
+         (guess (caddr triplet))
          (guess (if (functionp guess)
                     (funcall guess interactive)
                   guess))
@@ -945,7 +962,7 @@ be guessed."
                (cond (current-prefix-arg base-prompt)
                      ((null guess)
                       (format "[eglot] Sorry, couldn't guess for `%s'!\n%s"
-                              managed-mode base-prompt))
+                              main-mode base-prompt))
                      ((and program
                            (not (file-name-absolute-p program))
                            (not (eglot--executable-find program t)))
@@ -967,7 +984,7 @@ be guessed."
                      full-program-invocation
                      'eglot-command-history)))
               guess)))
-    (list managed-mode (eglot--current-project) class contact language-id)))
+    (list managed-modes (eglot--current-project) class contact language-id)))
 
 (defvar eglot-lsp-context)
 (put 'eglot-lsp-context 'variable-documentation
@@ -1038,7 +1055,7 @@ INTERACTIVE is t if called interactively."
   (interactive (list (eglot--current-server-or-lose) t))
   (when (jsonrpc-running-p server)
     (ignore-errors (eglot-shutdown server interactive nil 'preserve-buffers)))
-  (eglot--connect (eglot--major-mode server)
+  (eglot--connect (eglot--major-modes server)
                   (eglot--project server)
                   (eieio-object-class-name server)
                   (eglot--saved-initargs server)
@@ -1115,12 +1132,12 @@ Each function is passed the server as an argument")
 (defvar-local eglot--cached-server nil
   "A cached reference to the current EGLOT server.")
 
-(defun eglot--connect (managed-major-mode project class contact language-id)
-  "Connect to MANAGED-MAJOR-MODE, LANGUAGE-ID, PROJECT, CLASS and CONTACT.
+(defun eglot--connect (managed-modes project class contact language-id)
+  "Connect to MANAGED-MODES, LANGUAGE-ID, PROJECT, CLASS and CONTACT.
 This docstring appeases checkdoc, that's all."
   (let* ((default-directory (project-root project))
          (nickname (file-name-base (directory-file-name default-directory)))
-         (readable-name (format "EGLOT (%s/%s)" nickname managed-major-mode))
+         (readable-name (format "EGLOT (%s/%s)" nickname managed-modes))
          autostart-inferior-process
          server-info
          (contact (if (functionp contact) (funcall contact) contact))
@@ -1180,7 +1197,7 @@ This docstring appeases checkdoc, that's all."
     (setf (eglot--saved-initargs server) initargs)
     (setf (eglot--project server) project)
     (setf (eglot--project-nickname server) nickname)
-    (setf (eglot--major-mode server) managed-major-mode)
+    (setf (eglot--major-modes server) (eglot--ensure-list managed-modes))
     (setf (eglot--language-id server) language-id)
     (setf (eglot--inferior-process server) autostart-inferior-process)
     (run-hook-with-args 'eglot-server-initialized-hook server)
@@ -1236,7 +1253,7 @@ This docstring appeases checkdoc, that's all."
                                      (setf (eglot--inhibit-autoreconnect server)
                                            (null eglot-autoreconnect)))))))
                           (let ((default-directory (project-root project))
-                                (major-mode managed-major-mode))
+                                (major-mode (car managed-modes)))
                             (hack-dir-local-variables-non-file-buffer)
                             (run-hook-with-args 'eglot-connect-hook server))
                           (eglot--message
@@ -1244,7 +1261,7 @@ This docstring appeases checkdoc, that's all."
 in project `%s'."
                            (or (plist-get serverInfo :name)
                                (jsonrpc-name server))
-                           managed-major-mode
+                           managed-modes
                            (eglot-project-nickname server))
                           (when tag (throw tag t))))
                       :timeout eglot-connect-timeout
@@ -1545,8 +1562,8 @@ and just return it.  PROMPT shouldn't end with a question mark."
                           being hash-values of eglot--servers-by-project
                           append servers))
         (name (lambda (srv)
-                (format "%s/%s" (eglot-project-nickname srv)
-                        (eglot--major-mode srv)))))
+                (format "%s %s" (eglot-project-nickname srv)
+                        (eglot--major-modes srv)))))
     (cond ((null servers)
            (eglot--error "No servers!"))
           ((or (cdr servers) (not dont-if-just-the-one))
@@ -1570,6 +1587,8 @@ and just return it.  PROMPT shouldn't end with a question mark."
 (defun eglot--plist-keys (plist) "Get keys of a plist."
   (cl-loop for (k _v) on plist by #'cddr collect k))
 
+(defun eglot--ensure-list (x) (if (listp x) x (list x)))
+
 \f
 ;;; Minor modes
 ;;;
@@ -1700,7 +1719,8 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
         (or eglot--cached-server
             (cl-find major-mode
                      (gethash (eglot--current-project) eglot--servers-by-project)
-                     :key #'eglot--major-mode)
+                     :key #'eglot--major-modes
+                     :test #'memq)
             (and eglot-extend-to-xref
                  buffer-file-name
                  (gethash (expand-file-name buffer-file-name)
@@ -2288,7 +2308,7 @@ When called interactively, use the currently active server"
                                 (file-directory-p uri-path))
                            (file-name-as-directory uri-path)
                          (project-root (eglot--project server)))))
-                (setq-local major-mode (eglot--major-mode server))
+                (setq-local major-mode (car (eglot--major-modes server)))
                 (hack-dir-local-variables-non-file-buffer)
                 (cl-loop for (wsection o)
                          on (eglot--workspace-configuration-plist server)