branch: externals/elpa commit d1cdcf163849ca1a7bfcbc3287f4e2c28cae77af Author: João Távora <joaotav...@gmail.com> Commit: João Távora <joaotav...@gmail.com>
Friendlier M-x eglot * eglot.el (eglot-server-programs): Renamed from eglot-executables (eglot--interactive): Redesign (eglot): Docstring. (eglot--connect): Now a synchronous gig. (eglot--interactive): Friendlier. (eglot): Improve docstring, rework a bit. (eglot-reconnect): Rework a bit. (eglot--process-sentinel): Insert "byebye" ruler here. * README.md: Update --- README.md | 39 ++++++++++------- eglot.el | 140 +++++++++++++++++++++++++++++++------------------------------- 2 files changed, 94 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 66f2131..c2931be 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,25 @@ Eglot M-x eglot ``` -*That's it*. Either this guesses the LSP executable to start for the -language of your choice, or it prompts you to enter the program. +*That's it*. If you're lucky, this guesses the LSP executable to start +for the language of your choice, or it prompts you to enter one: -If you have these programs installed, `M-x eglot` works out-of-the-box -with: +`M-x eglot` currently guesses and works out-of-the-box with: * Javascript's [javascript-typescript-stdio][javascript-typescript-langserver] * Rust's [rls][rls] * Python's [pyls][pyls] +* Bash's [bash-language-server][bash-language-server] -You can also enter a `server:port` pattern to connect to an LSP +I'll add more as I test more features. In the meantime you can +customize `eglot-server-programs`: + +```lisp +(add-to-list 'eglot-server-programs '(fancy-mode . ("fancy-language-server" "--args""))) +``` + +Let me know how well it works and I'll add it to the list, or submit a +PR. You can also enter a `server:port` pattern to connect to an LSP server. To skip the guess and always be prompted use `C-u M-x eglot`. # Supported Protocol features @@ -89,21 +97,22 @@ server. To skip the guess and always be prompted use `C-u M-x eglot`. # Differences to lsp-mode.el -Eglot is **really beta** and may currently underperform -[lsp-mode.el][emacs-lsp], which is more mature and has a host of -[plugins][emacs-lsp-plugins] for bells and whistles. If you think the minimalist approach -of `eglot.el` is not for you, you could be better served with -`lsp-mode.el` for now. +Eglot is **beta**. It may currently underperform +[lsp-mode.el][emacs-lsp], both in functionality and correctness. That +other extension is much more mature and has a host of +[plugins][emacs-lsp-plugins] for bells and whistles. If you don't +like the minimalist approach of `eglot.el`, you could be better served +with `lsp-mode.el` for now. User-visible differences: -- Single entry point `M-x eglot`, not `M-x eglot-<language>`. Also no - `eglot-<language>` extra packages. +- Single and friendly entry point `M-x eglot`, not `M-x + eglot-<language>`. Also no `eglot-<language>` extra packages. - No "whitelisting" or "blacklisting" directories to languages. `M-x eglot` starts servers to handle major modes inside a specific project. Uses Emacs's built-in `project.el` library to discover - projects. Automatically detects current and future opened files under that - project and syncs with server. + projects. Automatically detects current and future opened files + under that project and syncs with server. - Easy way to quit/restart a server, just middle/right click on the connection name. - Pretty interactive mode-line section for live tracking of server @@ -134,6 +143,6 @@ Under the hood: [javascript-typescript-langserver]: https://github.com/sourcegraph/javascript-typescript-langserver [emacs-lsp]: https://github.com/emacs-lsp/lsp-mode [emacs-lsp-plugins]: https://github.com/emacs-lsp - +[bash-language-server]: https://github.com/mads-hartmann/bash-language-server diff --git a/eglot.el b/eglot.el index b2709a6..22799cc 100644 --- a/eglot.el +++ b/eglot.el @@ -47,9 +47,10 @@ :prefix "eglot-" :group 'applications) -(defvar eglot-executables '((rust-mode . ("rls")) - (python-mode . ("pyls")) - (js-mode . ("javascript-typescript-stdio"))) +(defvar eglot-server-programs '((rust-mode . ("rls")) + (python-mode . ("pyls")) + (js-mode . ("javascript-typescript-stdio")) + (sh-mode . ("bash-language-server" "start"))) "Alist mapping major modes to server executables.") (defface eglot-mode-line @@ -204,10 +205,8 @@ CONTACT is as `eglot--contact'. Returns a process object." :publishDiagnostics `(:relatedInformation :json-false)) :experimental (eglot--obj))) -(defun eglot--connect (project managed-major-mode - short-name contact &optional success-fn) - "Connect for PROJECT, MANAGED-MAJOR-MODE, SHORT-NAME and CONTACT. -SUCCESS-FN with no args if all goes well." +(defun eglot--connect (project managed-major-mode short-name contact) + "Connect for PROJECT, MANAGED-MAJOR-MODE, SHORT-NAME and CONTACT." (let* ((proc (eglot--make-process short-name managed-major-mode contact)) (buffer (process-buffer proc))) (setf (eglot--contact proc) contact @@ -216,70 +215,68 @@ SUCCESS-FN with no args if all goes well." (with-current-buffer buffer (let ((inhibit-read-only t)) (setf (eglot--short-name proc) short-name) - (push proc - (gethash (project-current) - eglot--processes-by-project)) + (push proc (gethash project eglot--processes-by-project)) (erase-buffer) (read-only-mode t) - (with-current-buffer (eglot-events-buffer proc) - (let ((inhibit-read-only t)) - (insert - (format "\n-----------------------------------\n")))) - (eglot--request - proc - :initialize - (eglot--obj :processId (unless (eq (process-type proc) - 'network) - (emacs-pid)) - :rootUri (eglot--path-to-uri - (car (project-roots (project-current)))) - :initializationOptions [] - :capabilities (eglot--client-capabilities)) - :success-fn - (cl-function - (lambda (&key capabilities) - (setf (eglot--capabilities proc) capabilities) - (setf (eglot--status proc) nil) - (dolist (buffer (buffer-list)) - (with-current-buffer buffer - (eglot--maybe-activate-editing-mode proc))) - (when success-fn (funcall success-fn proc)) - (eglot--notify proc :initialized (eglot--obj :__dummy__ t))))))))) + (cl-destructuring-bind (&key capabilities) + (eglot--sync-request + proc + :initialize + (eglot--obj :processId (unless (eq (process-type proc) + 'network) + (emacs-pid)) + :rootUri (eglot--path-to-uri + (car (project-roots project))) + :initializationOptions [] + :capabilities (eglot--client-capabilities))) + (setf (eglot--capabilities proc) capabilities) + (setf (eglot--status proc) nil) + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (eglot--maybe-activate-editing-mode proc))) + (eglot--notify proc :initialized (eglot--obj :__dummy__ t)) + proc))))) (defvar eglot--command-history nil "History of COMMAND arguments to `eglot'.") (defun eglot--interactive () "Helper for `eglot'." - (let* ((managed-major-mode + (let* ((guessed-mode (if buffer-file-name major-mode)) + (managed-mode (cond - ((or current-prefix-arg - (not buffer-file-name)) + ((or (>= (prefix-numeric-value current-prefix-arg) 16) + (not guessed-mode)) (intern (completing-read "[eglot] Start a server to manage buffers of what major mode? " (mapcar #'symbol-name (eglot--all-major-modes)) nil t - (symbol-name major-mode) nil - (symbol-name major-mode) nil))) - (t major-mode))) - (guessed-command - (cdr (assoc managed-major-mode eglot-executables)))) + (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil))) + (t guessed-mode))) + (guessed-command (cdr (assoc managed-mode eglot-server-programs))) + (base-prompt "[eglot] Enter program to execute (or <host>:<port>): ") + (prompt + (cond (current-prefix-arg base-prompt) + ((null guessed-command) + (concat (format "[eglot] Sorry, couldn't guess for `%s'!" + managed-mode) + "\n" base-prompt)) + ((and (listp 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-major-mode - (let ((prompt - (cond (current-prefix-arg - "[eglot] Enter program to execute (or <host>:<port>): ") - ((null guessed-command) - (format "[eglot] Sorry, couldn't guess for `%s'!\n\ -Enter program to execute (or <host>:<port>): " - managed-major-mode))))) - (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)) + managed-mode + (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))) ;;;###autoload @@ -296,8 +293,11 @@ is also know as the server's \"contact\". MANAGED-MAJOR-MODE is an Emacs major mode. -With a prefix arg, prompt for MANAGED-MAJOR-MODE and COMMAND, -else guess them from current context and `eglot-executables'. +Interactively, guess MANAGED-MAJOR-MODE from current buffer and +COMMAND from `eglot-server-programs'. With a single +\\[universal-argument] prefix arg, prompt for COMMAND. With two +\\[universal-argument] prefix args, also prompt for +MANAGED-MAJOR-MODE. INTERACTIVE is t if called interactively." (interactive (eglot--interactive)) @@ -313,16 +313,13 @@ INTERACTIVE is t if called interactively." (eglot-reconnect current-process interactive) (when (process-live-p current-process) (eglot-shutdown current-process)) - (eglot--connect project - managed-major-mode - short-name - command - (lambda (proc) - (eglot--message "Connected! Process `%s' now \ + (let ((proc (eglot--connect project + managed-major-mode + short-name + command))) + (eglot--message "Connected! Process `%s' now \ managing `%s' buffers in project `%s'." - proc - managed-major-mode - short-name))))))) + proc managed-major-mode short-name)))))) (defun eglot-reconnect (process &optional interactive) "Reconnect to PROCESS. @@ -333,8 +330,8 @@ INTERACTIVE is t if called interactively." (eglot--connect (eglot--project process) (eglot--major-mode process) (eglot--short-name process) - (eglot--contact process) - (lambda (_proc) (eglot--message "Reconnected!")))) + (eglot--contact process)) + (eglot--message "Reconnected!")) (defvar eglot--inhibit-auto-reconnect nil "If non-nil, don't autoreconnect on unexpected quit.") @@ -343,6 +340,9 @@ INTERACTIVE is t if called interactively." "Called when PROC undergoes CHANGE." (eglot--log-event proc `(:message "Process state changed" :change ,change)) (when (not (process-live-p proc)) + (with-current-buffer (eglot-events-buffer proc) + (let ((inhibit-read-only t)) + (insert "\n----------b---y---e---b---y---e----------\n"))) ;; Cancel outstanding timers (maphash (lambda (_id triplet) (cl-destructuring-bind (_success _error timeout) triplet