branch: externals/eglot commit 33ae871b957fe9727ee3230c64ccec693cdb52fd Author: João Távora <joaotav...@gmail.com> Commit: João Távora <joaotav...@gmail.com>
More flexible jrpc.el and improve eglot.el's doc Generalize and rework CONTACT arg to jrpc-connect * eglot.el (eglot--command-history): Tweak docstring. (eglot--interactive): Rework. (eglot): Rework docstring. COMMAND is now CONTACT. (eglot--connect): Use new jrpc-connect protocol. (eglot-server-programs): Reword doc. * jrpc.el (jrpc--make-process): Use new form of CONTACT. (jrpc-connect): Explain new semantics of CONTACT. --- eglot.el | 66 +++++++++++++++++++++++++++++++++++++--------------------------- jrpc.el | 53 ++++++++++++++++++++++++++------------------------- 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/eglot.el b/eglot.el index 13c1b49..4f2e25b 100644 --- a/eglot.el +++ b/eglot.el @@ -75,7 +75,9 @@ (sh-mode . ("bash-language-server" "start")) (php-mode . ("php" "vendor/felixfbecker/\ language-server/bin/php-language-server.php"))) - "Alist mapping major modes to server executables.") + "Alist of (MAJOR-MODE . CONTACT) mapping major modes to server executables. +CONTACT can be anything accepted by that parameter in the +function `eglot', which see.") (defface eglot-mode-line '((t (:inherit font-lock-constant-face :weight bold))) @@ -198,7 +200,7 @@ called interactively." :experimental (jrpc-obj))) (defvar eglot--command-history nil - "History of COMMAND arguments to `eglot'.") + "History of CONTACT arguments to `eglot'.") (defun eglot--interactive () "Helper for `eglot'." @@ -213,6 +215,7 @@ called interactively." (mapcar #'symbol-name (eglot--all-major-modes)) nil t (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil))) (t guessed-mode))) + (project (or (project-current) `(transient . ,default-directory))) (guessed-command (cdr (assoc managed-mode eglot-server-programs))) (base-prompt "[eglot] Enter program to execute (or <host>:<port>): ") (prompt @@ -222,26 +225,30 @@ called interactively." managed-mode) "\n" base-prompt)) ((and (listp guessed-command) + (not (integerp (cadr guessed-command))) (not (executable-find (car guessed-command)))) (concat (format "[eglot] I guess you want to run `%s'" (combine-and-quote-strings guessed-command)) (format ", but I can't find `%s' in PATH!" (car guessed-command)) - "\n" base-prompt))))) - (list - managed-mode - (or (project-current) `(transient . ,default-directory)) - (if prompt - (split-string-and-unquote - (read-shell-command prompt - (if (listp guessed-command) - (combine-and-quote-strings guessed-command)) - 'eglot-command-history)) - guessed-command) - t))) + "\n" base-prompt)))) + (contact + (cond ((not prompt) guessed-command) + (t + (let ((string (read-shell-command + prompt + (if (listp guessed-command) + (combine-and-quote-strings guessed-command)) + 'eglot-command-history))) + (if (and string (string-match + "^\\([^\s\t]+\\):\\([[:digit:]]+\\)$" + (string-trim string))) + (list (match-string 1 string) (match-string 2 string)) + (split-string-and-unquote string))))))) + (list managed-mode project contact t))) ;;;###autoload -(defun eglot (managed-major-mode project command &optional interactive) +(defun eglot (managed-major-mode project contact &optional interactive) "Manage a project with a Language Server Protocol (LSP) server. The LSP server is started (or contacted) via COMMAND. If this @@ -253,7 +260,7 @@ code-analysis via `xref-find-definitions', `flymake-mode', `eldoc-mode', `completion-at-point', among others. Interactively, the command attempts to guess MANAGED-MAJOR-MODE -from current buffer, COMMAND from `eglot-server-programs' and +from current buffer, CONTACT from `eglot-server-programs' and PROJECT from `project-current'. If it can't guess, the user is prompted. With a single \\[universal-argument] prefix arg, it always prompt for COMMAND. With two \\[universal-argument] @@ -261,11 +268,14 @@ prefix args, also prompts for MANAGED-MAJOR-MODE. PROJECT is a project instance as returned by `project-current'. -COMMAND is a list of strings, an executable program and -optionally its arguments. If the first and only string in the -list is of the form \"<host>:<port>\" it is taken as an -indication to connect to a server instead of starting one. This -is also know as the server's \"contact\". +CONTACT is a list of strings (COMMAND [ARGS...]) specifying how +to start a server subprocess to connect to. If the second +element in the list is an integer number instead of a string, the +list is interpreted as (HOST PORT [PARAMETERS...]) to connect to +an existing server via TCP, the remaining PARAMETERS being given +as `open-network-stream's optional arguments. CONTACT can also +be a function of no arguments returning a live connected process +object. MANAGED-MAJOR-MODE is an Emacs major mode. @@ -282,7 +292,7 @@ INTERACTIVE is t if called interactively." (let ((proc (eglot--connect project managed-major-mode (format "%s/%s" short-name managed-major-mode) - command))) + contact))) (eglot--message "Connected! Process `%s' now \ managing `%s' buffers in project `%s'." proc managed-major-mode short-name) @@ -310,13 +320,13 @@ Builds a function from METHOD, passes it PROC, ID and PARAMS." (let* ((handler-sym (intern (concat "eglot--server-" method)))) (if (functionp handler-sym) (apply handler-sym proc (append params (if id `(:id ,id)))) - (jrpc-reply - proc id + (jrpc-reply proc id :error (jrpc-obj :code -32601 :message "Unimplemented"))))) -(defun eglot--connect (project managed-major-mode name command) - (let ((proc (jrpc-connect name command #'eglot--dispatch #'eglot--on-shutdown)) - success) +(defun eglot--connect (project managed-major-mode name contact) + (let* ((contact (if (functionp contact) (funcall contact) contact)) + (proc (jrpc-connect name contact #'eglot--dispatch #'eglot--on-shutdown)) + success) (setf (eglot--project proc) project) (setf (eglot--major-mode proc)managed-major-mode) (push proc (gethash project eglot--processes-by-project)) @@ -350,7 +360,7 @@ Builds a function from METHOD, passes it PROC, ID and PARAMS." (null eglot-autoreconnect))))))) (setq success proc)) (unless (or success (not (process-live-p proc)) (eglot--moribund proc)) - (eglot-shutdown proc))))) + (eglot-shutdown proc))))) (defun eglot--server-ready-p (_what _proc) "Tell if server of PROC ready for processing deferred WHAT." diff --git a/jrpc.el b/jrpc.el index 6773e12..3467b89 100644 --- a/jrpc.el +++ b/jrpc.el @@ -128,26 +128,23 @@ A function passed the process object for the server.") (defun jrpc--make-process (name contact) "Make a process from CONTACT. NAME is a name to give the inferior process or connection. -CONTACT is as `jrpc-contact'. Returns a process object." +CONTACT is as explained in `jrpc-connect'. Returns a process +object." (let* ((readable-name (format "JSON-RPC server (%s)" name) ) - (buffer (get-buffer-create - (format "*%s inferior*" readable-name))) - singleton + (buffer (get-buffer-create (format "*%s inferior*" readable-name))) (proc - (if (and (setq singleton (and (null (cdr contact)) (car contact))) - (string-match "^[\s\t]*\\(.*\\):\\([[:digit:]]+\\)[\s\t]*$" - singleton)) - (open-network-stream readable-name - buffer - (match-string 1 singleton) - (string-to-number - (match-string 2 singleton))) - (make-process :name readable-name - :buffer buffer - :command contact - :connection-type 'pipe - :stderr (get-buffer-create (format "*%s stderr*" - name)))))) + (cond ((processp contact) contact) + ((integerp (cadr contact)) + (apply #'open-network-stream + readable-name buffer contact)) + (t + (make-process :name readable-name + :command contact + :connection-type 'pipe + :stderr (get-buffer-create (format "*%s stderr*" + name))))))) + (set-process-buffer proc buffer) + (set-marker (process-mark proc) (with-current-buffer buffer (point-min))) (set-process-filter proc #'jrpc--process-filter) (set-process-sentinel proc #'jrpc--process-sentinel) proc)) @@ -164,9 +161,13 @@ CONTACT is as `jrpc-contact'. Returns a process object." NAME is a string naming the server. -CONTACT is either a list of strings (a shell command and -arguments), or a list of a single string of the form -<host>:<port>. +CONTACT is a list of strings (COMMAND ARGS...) specifying how to +start a server subprocess to connect to. If the second element +in the list is an integer number instead of a string, the list is +interpreted as (HOST PORT PARAMETERS...) to connect to an +existing server via TCP, with the remaining PARAMETERS are given +to `open-network-stream's optional arguments. CONTACT can also +be a live connected process object. ON-SHUTDOWN, when non-nil, is a function called on server exit and passed the moribund process object. @@ -453,13 +454,13 @@ timeout keeps counting." (puthash id (list (or success-fn (jrpc-lambda (&rest _ignored) - (jrpc-log-event - proc (jrpc-obj :message "success ignored" :id id)))) + (jrpc-log-event + proc (jrpc-obj :message "success ignored" :id id)))) (or error-fn (jrpc-lambda (&key code message &allow-other-keys) - (setf (jrpc-status proc) `(,message t)) - proc (jrpc-obj :message "error ignored, status set" - :id id :error code))) + (setf (jrpc-status proc) `(,message t)) + proc (jrpc-obj :message "error ignored, status set" + :id id :error code))) (funcall make-timeout)) (jrpc--request-continuations proc)) (jrpc--process-send proc (jrpc-obj :jsonrpc "2.0"