Skip to content

Commit 40b6f1c

Browse files
committed
Merge pull request #489 from hugoduncan/feature/tramp-jack-in
Enable cider-jack-in on tramp buffers
2 parents 6d8b360 + d928cfc commit 40b6f1c

File tree

3 files changed

+129
-13
lines changed

3 files changed

+129
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* Cider command uses `cider-known-endpoints`.
99
* [#490](https://github.com/clojure-emacs/cider/pull/490) Dedicated
1010
support for `company-mode` in `cider-complete-at-point`.
11+
* [#489](https://github.com/clojure-emacs/cider/issues/489) Enable
12+
cider-jack-in on tramp source buffers.
1113
* [#460](https://github.com/clojure-emacs/cider/issues/460) Support for
1214
cider-nrepl's complete middleware for CLJ/CLJS autocomplete.
1315
* [#465](https://github.com/clojure-emacs/cider/issues/465) Support for

cider.el

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,17 @@ start the server."
103103
(cmd (if project
104104
(format "cd %s && %s" project cider-server-command)
105105
cider-server-command))
106-
(process (start-process-shell-command
107-
"nrepl-server"
108-
(generate-new-buffer-name (nrepl-server-buffer-name))
109-
cmd)))
106+
(default-directory project-dir)
107+
(nrepl-buffer-name (generate-new-buffer-name
108+
(nrepl-server-buffer-name)))
109+
(process
110+
(progn
111+
;; the buffer has to be created before the proc:
112+
(get-buffer-create nrepl-buffer-name)
113+
(start-file-process-shell-command
114+
"nrepl-server"
115+
nrepl-buffer-name
116+
cmd))))
110117
(set-process-filter process 'nrepl-server-filter)
111118
(set-process-sentinel process 'nrepl-server-sentinel)
112119
(set-process-coding-system process 'utf-8-unix 'utf-8-unix)

nrepl-client.el

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,18 @@ The `nrepl-buffer-name-separator' separates cider-repl from the project name."
8080
:type 'string
8181
:group 'nrepl)
8282

83+
(defcustom nrepl-connection-endpoint
84+
'nrepl-connection-ssh-tunnel
85+
"A function that is called to determine command that will be run
86+
once an nrepl server process is running. Used to set up an ssh tunnel
87+
on remote connections.
88+
89+
The arguments are dir and port. The return value
90+
should be an `plist` of the form
91+
(:proc-buffer-name \"*buf*\" :hostname \"hostname\" :port 1234)"
92+
:type 'function
93+
:group 'nrepl)
94+
8395
(defvar nrepl-repl-requires-sexp "(clojure.core/apply clojure.core/require '[[clojure.repl :refer (source apropos dir pst doc find-doc)] [clojure.java.javadoc :refer (javadoc)] [clojure.pprint :refer (pp pprint)]])"
8496
"Things to require in the tooling session and the REPL buffer.")
8597

@@ -88,10 +100,12 @@ The `nrepl-buffer-name-separator' separates cider-repl from the project name."
88100
(defvar-local nrepl-repl-buffer nil)
89101
(defvar-local nrepl-endpoint nil)
90102
(defvar-local nrepl-project-dir nil)
103+
(defvar-local nrepl-on-connection-buffer nil)
91104

92105
(defconst nrepl-repl-buffer-name-template "*cider-repl%s*")
93106
(defconst nrepl-connection-buffer-name-template "*nrepl-connection%s*")
94107
(defconst nrepl-server-buffer-name-template "*nrepl-server%s*")
108+
(defconst nrepl-on-connection-buffer-name-template "*nrepl-on-connection%s*")
95109

96110
(defcustom nrepl-hide-special-buffers nil
97111
"Control the display of some special buffers in buffer switching commands.
@@ -134,6 +148,11 @@ connection port if `nrepl-buffer-name-show-port' is true."
134148
(nrepl-apply-hide-special-buffers
135149
(nrepl-buffer-name nrepl-server-buffer-name-template)))
136150

151+
(defun nrepl-on-connection-buffer-name ()
152+
"Return the name of the on-connection buffer."
153+
(nrepl-apply-hide-special-buffers
154+
(nrepl-buffer-name nrepl-on-connection-buffer-name-template)))
155+
137156
;; buffer local declarations
138157
(defvar-local nrepl-session nil
139158
"Current nREPL session id.")
@@ -463,6 +482,8 @@ Also closes associated REPL and server buffers."
463482
(when (buffer-live-p buffer)
464483
(dolist (buf-name `(,(buffer-local-value 'nrepl-repl-buffer buffer)
465484
,(buffer-local-value 'nrepl-server-buffer buffer)
485+
,(buffer-local-value
486+
'nrepl-on-connection-buffer buffer)
466487
,buffer))
467488
(when buf-name
468489
(cider--close-buffer buf-name)))))))
@@ -695,6 +716,26 @@ are processed."
695716
(nrepl-send-request-sync (nrepl-eval-request input ns session)))
696717

697718
;;; server
719+
(defun nrepl--default-endpoint (dir port)
720+
"The endpoint for a repl in project DIR on PORT.
721+
Return a plist with :hostname, :port and :proc keys."
722+
(list :hostname (if (file-remote-p dir)
723+
tramp-current-host
724+
"localhost")
725+
:port port
726+
:proc-buffer-name nil))
727+
728+
(defun nrepl--endpoint-for-connection (dir port)
729+
"Call any `nrepl-connection-endpoint' for DIR and PORT.
730+
Return a plist with :hostname and :port values, specifying where
731+
to connect, and a :proc-buffer-name key, specifying the name of a
732+
process buffer to associate with the connection. When no
733+
`nrepl-connection-endpoint' is specified, returns a plist with
734+
the hostname associated with DIR, and PORT."
735+
(if (functionp nrepl-connection-endpoint)
736+
(funcall nrepl-connection-endpoint dir port)
737+
(nrepl--default-endpoint dir port)))
738+
698739
(defun nrepl-server-filter (process output)
699740
"Process nREPL server output from PROCESS contained in OUTPUT."
700741
(with-current-buffer (process-buffer process)
@@ -705,15 +746,21 @@ are processed."
705746
(let ((port (string-to-number (match-string 1 output))))
706747
(message (format "nREPL server started on %s" port))
707748
(with-current-buffer (process-buffer process)
708-
(let ((nrepl-process (nrepl-connect "localhost" port)))
709-
(setq nrepl-connection-buffer
710-
(buffer-name (process-buffer nrepl-process)))
711-
(with-current-buffer (process-buffer nrepl-process)
712-
(setq nrepl-server-buffer
713-
(buffer-name (process-buffer process))
714-
nrepl-project-dir
715-
(buffer-local-value
716-
'nrepl-project-dir (process-buffer process)))))))))
749+
(let* ((endpoint (nrepl--endpoint-for-connection
750+
default-directory port))
751+
(hostname (plist-get endpoint :hostname))
752+
(port (plist-get endpoint :port))
753+
(proc-buffer-name (plist-get endpoint :proc-buffer-name)))
754+
(let ((nrepl-process (nrepl-connect hostname port)))
755+
(setq nrepl-connection-buffer
756+
(buffer-name (process-buffer nrepl-process)))
757+
(with-current-buffer (process-buffer nrepl-process)
758+
(setq nrepl-server-buffer
759+
(buffer-name (process-buffer process))
760+
nrepl-project-dir
761+
(buffer-local-value
762+
'nrepl-project-dir (process-buffer process))
763+
nrepl-on-connection-buffer proc-buffer-name))))))))
717764

718765
(defun nrepl-server-sentinel (process event)
719766
"Handle nREPL server PROCESS EVENT."
@@ -735,6 +782,66 @@ are processed."
735782
(error "Leiningen 2.x is required by CIDER"))
736783
(t (error "Could not start nREPL server: %s" problem)))))
737784

785+
(defun nrepl--ssh-tunnel-command (ssh dir port)
786+
"Command string to open SSH tunnel to the host associated with DIR's PORT."
787+
(with-parsed-tramp-file-name dir nil
788+
(format-spec
789+
"%s -v -N -L %p:localhost:%p %u'%h'"
790+
`((?s . ,ssh)
791+
(?p . ,port)
792+
(?h . ,host)
793+
(?u . ,(if user (format "-l '%s' " user) ""))))))
794+
795+
(defun nrepl--ssh-tunnel-filter (port)
796+
"Return a filter function for waiting on PORT to appear in output."
797+
(let ((port-string (format "LOCALHOST:%s" port)))
798+
(lambda (proc string)
799+
(when (buffer-live-p (process-buffer proc))
800+
(with-current-buffer (process-buffer proc)
801+
(let ((moving (= (point) (process-mark proc))))
802+
(save-excursion
803+
(goto-char (process-mark proc))
804+
(insert string)
805+
(set-marker (process-mark proc) (point)))
806+
(if moving (goto-char (process-mark proc))))))
807+
(when (string-match port-string string)
808+
(with-current-buffer (process-buffer proc)
809+
(setq nrepl-wait-for-port nil))))))
810+
811+
(defun nrepl-connection-ssh-tunnel (dir port)
812+
"Return an endpoint for SSH tunnel to project DIR path, and PORT port.
813+
If DIR is remote, then attempt to open an SSH tunnel to port. If
814+
the ssh executable is not found on the path, then fall back to
815+
specifying a direct conneciton."
816+
;; this abuses the -v option for ssh to get output when the port
817+
;; forwarding is set up, which is used to synchronise on, so that
818+
;; the port forwarding is up when we try to connect.
819+
(if (file-remote-p dir)
820+
(let ((ssh (executable-find "ssh")))
821+
(if ssh
822+
;; run cmd in a local shell
823+
(let* ((cmd (nrepl--ssh-tunnel-command ssh dir port))
824+
(on-connection-buffer-name (nrepl-on-connection-buffer-name))
825+
(proc (start-process-shell-command
826+
"nrepl-on-connection"
827+
on-connection-buffer-name
828+
cmd))
829+
(on-connection-buffer (get-buffer
830+
on-connection-buffer-name)))
831+
(with-current-buffer on-connection-buffer-name
832+
(setq-local nrepl-wait-for-port t))
833+
(set-process-filter proc (nrepl--ssh-tunnel-filter port))
834+
(while (and (buffer-local-value 'nrepl-wait-for-port
835+
on-connection-buffer)
836+
(process-live-p proc))
837+
(accept-process-output nil 0.005))
838+
(unless (process-live-p proc)
839+
(message "SSH port forwarding failed"))
840+
(list :hostname "localhost" :port port
841+
:proc-buffer-name on-connection-buffer-name))
842+
(nrepl--default-endpoint dir port)))
843+
(list :hostname "localhost" :port port :proc-buffer-name nil)))
844+
738845
(defun nrepl-current-dir ()
739846
"Return the directory of the current buffer."
740847
(let ((file-name (buffer-file-name (current-buffer))))

0 commit comments

Comments
 (0)