From 2076d345655206254f6749cf710c150dfec313dd Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Tue, 2 Mar 2021 16:13:07 -0500 Subject: [PATCH] Add tramp support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Also close https://github.com/joaotavora/eglot/issues/463, close https://github.com/joaotavora/eglot/issues/84. Thanks to Brian Cully for the original simple idea. The basic technique is to pass :file-handler t to make-process, then tweak eglot--uri-to-path and eglot--path-to-uri, along with some other functions, to be aware of "trampy" paths". Crucially, a "stty hack" was needed. It has been encapsulated in a new a new eglot--cmd helper, which contains a comment explaining the hack. Co-authored-by: João Távora * eglot.el (eglot--executable-find): Shim two-arg executable-find function only available on Emacs 27. (eglot--guess-contact): Use eglot--executable-find. (eglot--cmd): New helper. (eglot--connect): Use eglot--cmd. Use :file-handler arg to make-process. (eglot--connect, eglot--path-to-uri): Be aware of trampy file names. * eglot-tests.el (eglot-tests--auto-detect-running-server-1): New helper. (eglot--guessing-contact): Better mock for executable-find. (eglot--tramp-test): New test. * NEWS.md: mention TRAMP support. * README.md: mention TRAMP support. GitHub-reference: close https://github.com/joaotavora/eglot/issues/637 --- lisp/progmodes/eglot.el | 57 ++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 610e57b5ad0..e0896c85017 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -244,6 +244,10 @@ let the buffer grow forever." (defconst eglot--{} (make-hash-table) "The empty JSON object.") +(defun eglot--executable-find (command &optional remote) + "Like Emacs 27's `executable-find', ignore REMOTE on Emacs 26." + (if (>= emacs-major-version 27) (executable-find command remote) + (executable-find command))) ;;; Message verification helpers @@ -753,7 +757,7 @@ be guessed." ((null guess) (format "[eglot] Sorry, couldn't guess for `%s'!\n%s" managed-mode base-prompt)) - ((and program (not (executable-find program))) + ((and program (not (eglot--executable-find program t))) (concat (format "[eglot] I guess you want to run `%s'" program-guess) (format ", but I can't find `%s' in PATH!" program) @@ -878,6 +882,21 @@ received the initializing configuration. Each function is passed the server as an argument") +(defun eglot--cmd (contact) + "Helper for `eglot--connect'." + (if (file-remote-p default-directory) + ;; TODO: this seems like a bug, although it’s everywhere. For + ;; some reason, for remote connections only, over a pipe, we + ;; need to turn off line buffering on the tty. + ;; + ;; Not only does this seem like there should be a better way, + ;; but it almost certainly doesn’t work on non-unix systems. + (list "sh" "-c" + (string-join (cons "stty raw > /dev/null;" + (mapcar #'shell-quote-argument contact)) + " ")) + contact)) + (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." @@ -908,12 +927,13 @@ This docstring appeases checkdoc, that's all." (let ((default-directory default-directory)) (make-process :name readable-name - :command contact + :command (eglot--cmd contact) :connection-type 'pipe :coding 'utf-8-emacs-unix :noquery t :stderr (get-buffer-create - (format "*%s stderr*" readable-name))))))))) + (format "*%s stderr*" readable-name)) + :file-handler t))))))) (spread (lambda (fn) (lambda (server method params) (apply fn server method (append params nil))))) (server @@ -943,10 +963,15 @@ This docstring appeases checkdoc, that's all." (jsonrpc-async-request server :initialize - (list :processId (unless (eq (jsonrpc-process-type server) - 'network) - (emacs-pid)) - :rootPath (expand-file-name default-directory) + (list :processId + (unless (or (file-remote-p default-directory) + (eq (jsonrpc-process-type server) + 'network)) + (emacs-pid)) + ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py' + ;; into `/path/to/baz.py', so LSP groks it. + :rootPath (expand-file-name + (file-local-name default-directory)) :rootUri (eglot--path-to-uri default-directory) :initializationOptions (eglot-initialization-options server) @@ -1169,15 +1194,23 @@ If optional MARKER, return a marker instead" "URIfy PATH." (url-hexify-string (concat "file://" (if (eq system-type 'windows-nt) "/") - (directory-file-name (file-truename path))) + ;; Again watch out for trampy paths. + (directory-file-name (file-local-name (file-truename path)))) url-path-allowed-chars)) (defun eglot--uri-to-path (uri) - "Convert URI to a file path." + "Convert URI to file path, helped by `eglot--current-server'." (when (keywordp uri) (setq uri (substring (symbol-name uri) 1))) - (let ((retval (url-filename (url-generic-parse-url (url-unhex-string uri))))) - (if (and (eq system-type 'windows-nt) (cl-plusp (length retval))) - (substring retval 1) retval))) + (let* ((retval (url-filename (url-generic-parse-url (url-unhex-string uri)))) + (normalized (if (and (eq system-type 'windows-nt) + (cl-plusp (length retval))) + (substring retval 1) + retval)) + (server (eglot-current-server)) + (remote-prefix (and server + (file-remote-p + (project-root (eglot--project server)))))) + (concat remote-prefix normalized))) (defun eglot--snippet-expansion-fn () "Compute a function to expand snippets. -- 2.39.5