Skip to content

Commit 53cbca8

Browse files
committed
Fix lsp-mode's tramp support
- This fixes the implementation of `lsp-mode` tramp support. After this PR the remote clients will be automatically registered and in most of the cases it will work out of the box. The remote connection is managed to a way similar to what eglot does. Fixes emacs-lsp#4158 Fixes emacs-lsp#4150 Fixes emacs-lsp#4158 Fixes emacs-lsp#4150 Fixes emacs-lsp#3841 Fixes emacs-lsp#3642 Fixes emacs-lsp#3579 Fixes emacs-lsp#3530 Fixes emacs-lsp#3491 Fixes emacs-lsp#3490 Fixes emacs-lsp#3391 Fixes emacs-lsp#3369 Fixes emacs-lsp#3364 Fixes emacs-lsp#3020 Fixes emacs-lsp#3018 Fixes emacs-lsp#3020
1 parent 0f5723f commit 53cbca8

File tree

3 files changed

+66
-107
lines changed

3 files changed

+66
-107
lines changed

docs/manual-language-docs/lsp-rust-analyzer.md

-19
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,3 @@ In the example below, first you see that:
119119

120120
This [unmerged PR](https://github.com/emacs-lsp/lsp-mode/pull/1740) contains an example method that allows
121121
modifying the signature that is displayed by eldoc.
122-
123-
### TRAMP Example
124-
125-
The following is an example configuration for using lsp-mode with a remote rust-analyzer server:
126-
127-
```
128-
(with-eval-after-load "lsp-rust"
129-
(lsp-register-client
130-
(make-lsp-client
131-
:new-connection (lsp-tramp-connection "rust-analyzer")
132-
:remote? t
133-
:major-modes '(rust-mode rustic-mode)
134-
:initialization-options 'lsp-rust-analyzer--make-init-options
135-
:notification-handlers (ht<-alist lsp-rust-notification-handlers)
136-
:action-handlers (ht ("rust-analyzer.runSingle" #'lsp-rust--analyzer-run-single))
137-
:library-folders-fn (lambda (_workspace) lsp-rust-analyzer-library-directories)
138-
:ignore-messages nil
139-
:server-id 'rust-analyzer-remote)))
140-
```

docs/page/remote.md

+2-21
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,14 @@ root_file: docs/page/remote.md
55

66
## TRAMP
77

8-
LSP mode has support for tramp buffers with the following requirements:
8+
`lsp-mode` has support for tramp buffers with the following requirements:
99

1010
- The language server has to be present on the remote server.
1111
- Having multi folder language server (like [Eclipse JDT LS](https://github.com/eclipse/eclipse.jdt.ls)) cannot have local and remote workspace folders.
1212

1313
### How does it work?
1414

15-
`lsp-mode` detects whether a particular file is located on remote machine and looks for a client which matches current file and it is marked as `:remote?` t. Then `lsp-mode` starts the client through tramp.
16-
17-
### Sample configuration
18-
19-
Here it is example how you can configure python language server to work when using `TRAMP`. Note that if you are trying to convert existing language server configuration you should copy all of it's properties(e. g. `:request-handlers`, `activation-fn`, etc). Also, when you are doing that you should make sure that none of the custom language server settings are not pointing to local path because those settings will be sent to the remote server.
20-
21-
```elisp
22-
(lsp-register-client
23-
(make-lsp-client :new-connection (lsp-tramp-connection "<binary name (e. g. pyls, rls)>")
24-
:major-modes '(python-mode)
25-
:remote? t
26-
:server-id 'pyls-remote))
27-
```
28-
29-
_Note:_ when you do not have root privileges on the remote machine to put the language server on the path you may alter the remote path by changing `tramp-remote-path`.
30-
31-
### Dealing with stderr
32-
33-
With TRAMP, Emacs does not have an easy way to distinguish stdout and stderr, so when the underlying LSP process writes to stderr, it breaks the `lsp-mode` parser. As a workaround, `lsp-mode` is redirecting stderr to `/tmp/<process-name>-<id>~stderr`.
34-
15+
`lsp-mode` detects whether a particular file is located on remote machine and looks for a client which matches current file and it is marked as `:remote?` t. Then `lsp-mode` starts the client through tramp. By default `lsp-mode` will copy the local client and mark it as `remote? t`. In most of the cases it is good enough but certain cases this may not work (e. g. if the server configuration contains references to local paths). In this case the user is supposed to create `.dir-local` configuration to override the references to local paths or open an issue on `lsp-mode` side to make the setting remote agnostic. To turn of automatic remote clients registration you can set `lsp-auto-register-remote-clients` to `nil`.
3516

3617
## Docker
3718

lsp-mode.el

+64-67
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,7 @@ calling `remove-overlays'.")
11431143

11441144
(defvar-local lsp--virtual-buffer-point-max nil)
11451145

1146-
(cl-defgeneric lsp-execute-command (server command arguments)
1146+
(cl-defmethod lsp-execute-command (server command arguments)
11471147
"Ask SERVER to execute COMMAND with ARGUMENTS.")
11481148

11491149
(defun lsp-elt (sequence n)
@@ -5420,6 +5420,12 @@ In addition, each can have property:
54205420
:type 'boolean
54215421
:package-version '(lsp-mode . "8.0.1"))
54225422

5423+
(defcustom lsp-auto-register-remote-clients t
5424+
"When non-nil register remote when registering the local one."
5425+
:group 'lsp-mode
5426+
:type 'boolean
5427+
:package-version '(lsp-mode . "8.0.1"))
5428+
54235429
(defun lsp--display-inline-image (mode)
54245430
"Add image property if available."
54255431
(let ((plist-list (cdr (assq mode lsp--display-inline-image-alist))))
@@ -5522,7 +5528,7 @@ When language is nil render as markup if `markdown-mode' is loaded."
55225528
;; render the first line.
55235529
(_ (lsp-clients-extract-signature-on-hover contents nil))))
55245530

5525-
(cl-defgeneric lsp-clients-extract-signature-on-hover (contents _server-id)
5531+
(cl-defmethod lsp-clients-extract-signature-on-hover (contents _server-id)
55265532
"Extract a representative line from CONTENTS, to show in the echo area."
55275533
(car (s-lines (s-trim (lsp--render-element contents)))))
55285534

@@ -7283,15 +7289,21 @@ Return a nested alist keyed by symbol names. e.g.
72837289
(when menu-bar-mode
72847290
(lsp--imenu-refresh)))
72857291

7286-
(defun lsp-resolve-final-function (command)
7292+
(defun lsp-resolve-final-command (command &optional test?)
72877293
"Resolve final function COMMAND."
7288-
(-let [command (if (functionp command) (funcall command) command)]
7289-
(cl-etypecase command
7290-
(list
7291-
(cl-assert (seq-every-p (apply-partially #'stringp) command) nil
7292-
"Invalid command list")
7293-
command)
7294-
(string (list command)))))
7294+
(let* ((command (lsp-resolve-value command))
7295+
(command (cl-etypecase command
7296+
(list
7297+
(cl-assert (seq-every-p (apply-partially #'stringp) command) nil
7298+
"Invalid command list")
7299+
command)
7300+
(string (list command)))))
7301+
(if (and (file-remote-p default-directory) (not test?))
7302+
(list shell-file-name "-c"
7303+
(string-join (cons "stty raw > /dev/null;"
7304+
(mapcar #'shell-quote-argument command))
7305+
" "))
7306+
command)))
72957307

72967308
(defun lsp-server-present? (final-command)
72977309
"Check whether FINAL-COMMAND is present."
@@ -7341,6 +7353,17 @@ corresponding to PATH, else returns `default-directory'."
73417353
(lsp-workspace-root path)
73427354
default-directory))
73437355

7356+
(defun lsp--fix-remote-cmd (program)
7357+
"Helper for `lsp-stdio-connection'.
7358+
Originally coppied from eglot."
7359+
7360+
(if (file-remote-p default-directory)
7361+
(list shell-file-name "-c"
7362+
(string-join (cons "stty raw > /dev/null;"
7363+
(mapcar #'shell-quote-argument program))
7364+
" "))
7365+
program))
7366+
73447367
(defun lsp-stdio-connection (command &optional test-command)
73457368
"Returns a connection property list using COMMAND.
73467369
COMMAND can be: A string, denoting the command to launch the
@@ -7360,26 +7383,28 @@ returned by COMMAND is available via `executable-find'"
73607383
(stringp el))
73617384
l))))))
73627385
(list :connect (lambda (filter sentinel name environment-fn workspace)
7363-
(if (functionp 'json-rpc-connection)
7364-
(lsp-json-rpc-connection
7365-
workspace
7366-
(lsp-resolve-final-function command))
7367-
(let ((final-command (lsp-resolve-final-function command))
7386+
(if (and (functionp 'json-rpc-connection)
7387+
(not (file-remote-p default-directory)))
7388+
(lsp-json-rpc-connection workspace (lsp-resolve-final-command command))
7389+
(let ((final-command (lsp-resolve-final-command command))
73687390
(process-name (generate-new-buffer-name name))
73697391
(process-environment
73707392
(lsp--compute-process-environment environment-fn)))
7371-
(let* ((stderr-buf (format "*%s::stderr*" process-name))
7393+
(let* ((stderr-buf (get-buffer-create (format "*%s::stderr*" process-name)))
73727394
(default-directory (lsp--default-directory-for-connection))
7395+
(tramp-use-ssh-controlmaster-options 'suppress)
7396+
(tramp-ssh-controlmaster-options "-o ControlMaster=no -o ControlPath=none")
73737397
(proc (make-process
73747398
:name process-name
73757399
:connection-type 'pipe
73767400
:buffer (format "*%s*" process-name)
7377-
:coding 'no-conversion
7401+
:coding 'utf-8-emacs-unix
73787402
:command final-command
73797403
:filter filter
73807404
:sentinel sentinel
73817405
:stderr stderr-buf
7382-
:noquery t)))
7406+
:noquery t
7407+
:file-handler t)))
73837408
(set-process-query-on-exit-flag proc nil)
73847409
(set-process-query-on-exit-flag (get-buffer-process stderr-buf) nil)
73857410
(with-current-buffer (get-buffer stderr-buf)
@@ -7388,7 +7413,8 @@ returned by COMMAND is available via `executable-find'"
73887413
(cons proc proc)))))
73897414
:test? (or
73907415
test-command
7391-
(lambda () (-> command lsp-resolve-final-function lsp-server-present?)))))
7416+
(lambda ()
7417+
(lsp-server-present? (lsp-resolve-final-command command t))))))
73927418

73937419
(defun lsp--open-network-stream (host port name)
73947420
"Open network stream to HOST:PORT.
@@ -7504,35 +7530,7 @@ should return the command to start the LS server."
75047530
(cons tcp-client-connection cmd-proc)))
75057531
:test? (lambda () (executable-find (cl-first (funcall command-fn 0))))))
75067532

7507-
(defun lsp-tramp-connection (local-command &optional generate-error-file-fn)
7508-
"Create LSP stdio connection named name.
7509-
LOCAL-COMMAND is either list of strings, string or function which
7510-
returns the command to execute."
7511-
(list :connect (lambda (filter sentinel name environment-fn _workspace)
7512-
(let* ((final-command (lsp-resolve-final-function local-command))
7513-
;; wrap with stty to disable converting \r to \n
7514-
(process-name (generate-new-buffer-name name))
7515-
(wrapped-command (s-join
7516-
" "
7517-
(append '("stty" "raw" ";")
7518-
final-command
7519-
(list
7520-
(concat "2>"
7521-
(or (when generate-error-file-fn
7522-
(funcall generate-error-file-fn name))
7523-
(format "/tmp/%s-%s-stderr" name
7524-
(cl-incf lsp--stderr-index))))))))
7525-
(process-environment
7526-
(lsp--compute-process-environment environment-fn)))
7527-
(let ((proc (start-file-process-shell-command process-name
7528-
(format "*%s*" process-name)
7529-
wrapped-command)))
7530-
(set-process-sentinel proc sentinel)
7531-
(set-process-filter proc filter)
7532-
(set-process-query-on-exit-flag proc nil)
7533-
(set-process-coding-system proc 'binary 'binary)
7534-
(cons proc proc))))
7535-
:test? (lambda () (-> local-command lsp-resolve-final-function lsp-server-present?))))
7533+
(defalias 'lsp-tramp-connection 'lsp-stdio-connection)
75367534

75377535
(defun lsp--auto-configure ()
75387536
"Autoconfigure `company', `flycheck', `lsp-ui', etc if they are installed."
@@ -8360,10 +8358,7 @@ the next question until the queue is empty."
83608358
(->> lsp-clients hash-table-values (-filter pred)))
83618359

83628360
(defun lsp--find-clients ()
8363-
"Find clients which can handle BUFFER-MAJOR-MODE.
8364-
SESSION is the currently active session. The function will also
8365-
pick only remote enabled clients in case the FILE-NAME is on
8366-
remote machine and vice versa."
8361+
"Find clients which can handle current buffer."
83678362
(-when-let (matching-clients (lsp--filter-clients (-andfn #'lsp--supports-buffer?
83688363
#'lsp--server-binary-present?)))
83698364
(lsp-log "Found the following clients for %s: %s"
@@ -8396,23 +8391,25 @@ remote machine and vice versa."
83968391
(--each (lsp-session-folders (lsp-session))
83978392
(lsp-workspace-folders-remove it)))
83988393

8399-
84008394
(defun lsp-register-client (client)
84018395
"Registers LSP client CLIENT."
8402-
(cl-assert (symbolp (lsp--client-server-id client)) t)
8403-
(cl-assert (or
8404-
(functionp (lsp--client-activation-fn client))
8405-
(and (listp (lsp--client-major-modes client))
8406-
(seq-every-p (apply-partially #'symbolp)
8407-
(lsp--client-major-modes client))))
8408-
nil "Invalid activation-fn and/or major-modes.")
84098396
(let ((client-id (lsp--client-server-id client)))
84108397
(puthash client-id client lsp-clients)
84118398
(setplist (intern (format "lsp-%s-after-open-hook" client-id))
84128399
`( standard-value (nil) custom-type hook
84138400
custom-package-version (lsp-mode . "7.0.1")
84148401
variable-documentation ,(format "Hooks to run after `%s' server is run." client-id)
8415-
custom-requests nil))))
8402+
custom-requests nil)))
8403+
(when (and lsp-auto-register-remote-clients
8404+
(not (lsp--client-remote? client)))
8405+
(let ((remote-client (copy-lsp--client client)))
8406+
(setf (lsp--client-remote? remote-client) t
8407+
(lsp--client-server-id remote-client) (intern
8408+
(format "%s-tramp"
8409+
(lsp--client-server-id client)))
8410+
;; disable automatic download
8411+
(lsp--client-download-server-fn client) nil)
8412+
(lsp-register-client remote-client))))
84168413

84178414
(defun lsp--create-initialization-options (_session client)
84188415
"Create initialization-options from SESSION and CLIENT.
@@ -8579,24 +8576,24 @@ When ALL is t, erase all log buffers of the running session."
85798576

85808577

85818578

8582-
(cl-defgeneric lsp-process-id ((process process))
8579+
(cl-defmethod lsp-process-id ((process process))
85838580
(process-id process))
85848581

8585-
(cl-defgeneric lsp-process-name ((process process)) (process-name process))
8582+
(cl-defmethod lsp-process-name ((process process)) (process-name process))
85868583

8587-
(cl-defgeneric lsp-process-status ((process process)) (process-status process))
8584+
(cl-defmethod lsp-process-status ((process process)) (process-status process))
85888585

8589-
(cl-defgeneric lsp-process-kill ((process process))
8586+
(cl-defmethod lsp-process-kill ((process process))
85908587
(when (process-live-p process)
85918588
(kill-process process)))
85928589

8593-
(cl-defgeneric lsp-process-send ((process process) message)
8590+
(cl-defmethod lsp-process-send ((process process) message)
85948591
(condition-case err
85958592
(process-send-string process (lsp--make-message message))
85968593
('error (lsp--error "Sending to process failed with the following error: %s"
85978594
(error-message-string err)))))
85988595

8599-
(cl-defgeneric lsp-process-cleanup (process)
8596+
(cl-defmethod lsp-process-cleanup (process)
86008597
;; Kill standard error buffer only if the process exited normally.
86018598
;; Leave it intact otherwise for debugging purposes.
86028599
(let ((buffer (-> process process-name get-buffer)))

0 commit comments

Comments
 (0)