branch: externals/eglot commit 6531c8b208736c9f30f829608791deaa44a9160f Merge: 7371f68 1506172 Author: João Távora <joaotav...@gmail.com> Commit: João Távora <joaotav...@gmail.com>
Merge branch 'master' into jsonrpc-refactor --- eglot-tests.el | 19 +++++++----- eglot.el | 94 +++++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 72 insertions(+), 41 deletions(-) diff --git a/eglot-tests.el b/eglot-tests.el index 56ec980..b57b949 100644 --- a/eglot-tests.el +++ b/eglot-tests.el @@ -181,6 +181,9 @@ Pass TIMEOUT to `eglot--with-timeout'." (define-derived-mode rust-mode prog-mode "Rust")) (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) +(defun eglot--tests-connect () + (apply #'eglot--connect (eglot--guess-contact))) + (ert-deftest auto-detect-running-server () "Visit a file and M-x eglot, then visit a neighbour. " (skip-unless (executable-find "rls")) @@ -192,7 +195,7 @@ Pass TIMEOUT to `eglot--with-timeout'." (eglot--with-timeout 2 (with-current-buffer (eglot--find-file-noselect "project/coiso.rs") - (should (setq server (apply #'eglot (eglot--interactive)))) + (should (setq server (eglot--tests-connect))) (should (eglot--current-server))) (with-current-buffer (eglot--find-file-noselect "project/merdix.rs") @@ -212,7 +215,7 @@ Pass TIMEOUT to `eglot--with-timeout'." (eglot--with-timeout 3 (with-current-buffer (eglot--find-file-noselect "project/coiso.rs") - (should (setq server (apply #'eglot (eglot--interactive)))) + (should (setq server (eglot--tests-connect))) ;; In 1.2 seconds > `eglot-autoreconnect' kill servers. We ;; should have a automatic reconnection. (run-with-timer 1.2 nil (lambda () (delete-process @@ -244,7 +247,7 @@ Pass TIMEOUT to `eglot--with-timeout'." :client-notifications c-notifs :client-replies c-replies ) - (should (apply #'eglot (eglot--interactive))) + (should (eglot--tests-connect)) (let (register-id) (eglot--wait-for (s-requests 1) (&key id method &allow-other-keys) @@ -274,7 +277,7 @@ Pass TIMEOUT to `eglot--with-timeout'." (eglot--find-file-noselect "diag-project/main.rs") (should (zerop (shell-command "cargo init"))) (eglot--sniffing (:server-notifications s-notifs) - (apply #'eglot (eglot--interactive)) + (eglot--tests-connect) (eglot--wait-for (s-notifs 1) (&key _id method &allow-other-keys) (string= method 'textDocument/publishDiagnostics)) @@ -304,7 +307,7 @@ Pass TIMEOUT to `eglot--with-timeout'." :client-replies c-replies :client-requests c-reqs ) - (apply #'eglot (eglot--interactive)) + (eglot--tests-connect) (goto-char (point-min)) (search-forward "return te") (insert "st") @@ -342,7 +345,7 @@ Pass TIMEOUT to `eglot--with-timeout'." :client-replies c-replies :client-requests c-reqs ) - (apply #'eglot (eglot--interactive)) + (eglot--tests-connect) (goto-char (point-min)) (search-forward "return te") (eglot-rename "bla") (should (equal (buffer-string) "fn test() -> i32 { let bla=3; return bla; }"))))))) @@ -355,7 +358,7 @@ Pass TIMEOUT to `eglot--with-timeout'." (eglot--with-timeout 4 (with-current-buffer (eglot--find-file-noselect "project/something.py") - (should (apply #'eglot (eglot--interactive))) + (should (eglot--tests-connect)) (goto-char (point-max)) (completion-at-point) (should (looking-back "sys.exit")))))) @@ -368,7 +371,7 @@ Pass TIMEOUT to `eglot--with-timeout'." (eglot--with-timeout 4 (with-current-buffer (eglot--find-file-noselect "project/something.py") - (should (apply #'eglot (eglot--interactive))) + (should (eglot--tests-connect)) (goto-char (point-max)) (setq eldoc-last-message nil) (completion-at-point) diff --git a/eglot.el b/eglot.el index 32879ec..0a060a5 100644 --- a/eglot.el +++ b/eglot.el @@ -46,6 +46,13 @@ ;; To "unmanage" these buffers, shutdown the server with M-x ;; eglot-shutdown. ;; +;; You can also do: +;; +;; (add-hook 'foo-mode-hook 'eglot-ensure) +;; +;; To attempt to start an eglot session automatically everytime a +;; foo-mode buffer is visited. +;; ;;; Code: (require 'json) @@ -255,8 +262,10 @@ function with the server still running." (defvar eglot--command-history nil "History of CONTACT arguments to `eglot'.") -(defun eglot--interactive () - "Helper for `eglot'." +(defun eglot--guess-contact (&optional interactive) + "Helper for `eglot'. +Return (MANAGED-MODE PROJECT CONTACT CLASS). +If INTERACTIVE, maybe prompt user." (let* ((guessed-mode (if buffer-file-name major-mode)) (managed-mode (cond @@ -274,17 +283,20 @@ function with the server still running." (prog1 (car guess) (setq guess (cdr guess))) 'eglot-lsp-server)) (program (and (listp guess) (stringp (car guess)) (car guess))) - (base-prompt "[eglot] Enter program to execute (or <host>:<port>): ") + (base-prompt + (and interactive + "[eglot] Enter program to execute (or <host>:<port>): ")) (prompt - (cond (current-prefix-arg base-prompt) - ((null guess) - (format "[eglot] Sorry, couldn't guess for `%s'\n%s!" - managed-mode base-prompt)) - ((and program (not (executable-find program))) - (concat (format "[eglot] I guess you want to run `%s'" - (combine-and-quote-strings guess)) - (format ", but I can't find `%s' in PATH!" program) - "\n" base-prompt)))) + (and base-prompt + (cond (current-prefix-arg base-prompt) + ((null guess) + (format "[eglot] Sorry, couldn't guess for `%s'!\n%s" + managed-mode base-prompt)) + ((and program (not (executable-find program))) + (concat (format "[eglot] I guess you want to run `%s'" + (combine-and-quote-strings guess)) + (format ", but I can't find `%s' in PATH!" program) + "\n" base-prompt))))) (contact (if prompt (let ((s (read-shell-command @@ -296,7 +308,7 @@ function with the server still running." (list (match-string 1 s) (string-to-number (match-string 2 s))) (split-string-and-unquote s))) guess))) - (list managed-mode project class contact t))) + (list managed-mode project class contact))) ;;;###autoload (defun eglot (managed-major-mode project class contact &optional interactive) @@ -327,26 +339,22 @@ keyword-value plist used to initialize CLASS or a plain list as described in `eglot-server-programs', which see. INTERACTIVE is t if called interactively." - (interactive (eglot--interactive)) - (let* ((nickname (file-name-base (directory-file-name - (car (project-roots project))))) - (current-server (eglot--current-server)) + (interactive (append (eglot--guess-contact t) '(t))) + (let* ((current-server (eglot--current-server)) (live-p (and current-server (jsonrpc-running-p current-server)))) (if (and live-p interactive (y-or-n-p "[eglot] Live process found, reconnect instead? ")) (eglot-reconnect current-server interactive) (when live-p (ignore-errors (eglot-shutdown current-server))) - (let ((server (eglot--connect project - managed-major-mode - (format "%s/%s" nickname managed-major-mode) - nickname + (let ((server (eglot--connect managed-major-mode + project class contact))) (eglot--message "Connected! Process `%s' now \ managing `%s' buffers in project `%s'." (jsonrpc-name server) managed-major-mode - nickname) + (eglot--project-nickname server)) server)))) (defun eglot-reconnect (server &optional interactive) @@ -355,14 +363,34 @@ 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))) - (eglot--connect (eglot--project server) - (eglot--major-mode server) - (jsonrpc-name server) - (eglot--project-nickname server) + (eglot--connect (eglot--major-mode server) + (eglot--project server) (eieio-object-class-name server) (eglot--saved-initargs server)) (eglot--message "Reconnected!")) +(defvar eglot--managed-mode) ; forward decl + +(defun eglot-ensure () + "Start Eglot session for current buffer if there isn't one." + (let ((buffer (current-buffer))) + (cl-labels + ((maybe-connect + () + (remove-hook 'post-command-hook #'maybe-connect nil) + (eglot--with-live-buffer buffer + (if eglot--managed-mode + (eglot--message "%s is already managed by existing `%s'" + buffer + (eglot--project-nickname (eglot--current-server))) + (let ((server (apply #'eglot--connect (eglot--guess-contact)))) + (eglot--message + "Automatically started `%s' to manage `%s' buffers in project `%s'" + (eglot--project-nickname server) + major-mode + (eglot--project-nickname server))))))) + (add-hook 'post-command-hook #'maybe-connect 'append nil)))) + (defun eglot-events-buffer (server) "Display events buffer for SERVER." (interactive (eglot--current-server-or-lose)) @@ -380,12 +408,12 @@ INTERACTIVE is t if called interactively." (defvar eglot-connect-hook nil "Hook run after connecting in `eglot--connect'.") -(defun eglot--connect (project managed-major-mode name nickname - class contact) - "Connect to PROJECT, MANAGED-MAJOR-MODE, NAME. -And don't forget NICKNAME and CLASS, CONTACT. This docstring -appeases checkdoc, that's all." - (let* ((readable-name (format "EGLOT (%s/%s)" nickname managed-major-mode)) +(defun eglot--connect (managed-major-mode project class contact) + "Connect to MANAGED-MAJOR-MODE, PROJECT, CLASS and CONTACT. +This docstring appeases checkdoc, that's all." + (let* ((nickname (file-name-base (directory-file-name + (car (project-roots project))))) + (readable-name (format "EGLOT (%s/%s)" nickname managed-major-mode)) (initargs (cond ((keywordp (car contact)) contact) ((integerp (cadr contact)) @@ -410,7 +438,7 @@ appeases checkdoc, that's all." (server (apply #'make-instance class - :name name + :name readable-name :notification-dispatcher (funcall spread #'eglot-handle-notification) :request-dispatcher (funcall spread #'eglot-handle-request) :on-shutdown #'eglot--on-shutdown