From: João Távora Date: Mon, 26 Sep 2022 10:43:39 +0000 (+0100) Subject: Add support for "single server, multiple modes" X-Git-Tag: emacs-29.0.90~1616^2~524^2~4^2~9 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=5b902b5cbb1de5f3852856d194deaf996c2623cc;p=emacs.git Add support for "single server, multiple modes" 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 --- diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index fa29c606179..6a1eb1282c7 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -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))) + ;;; 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)