@@ -80,6 +80,18 @@ The `nrepl-buffer-name-separator' separates cider-repl from the project name."
80
80
:type 'string
81
81
:group 'nrepl )
82
82
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
+
83
95
(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)]])"
84
96
" Things to require in the tooling session and the REPL buffer." )
85
97
@@ -88,10 +100,12 @@ The `nrepl-buffer-name-separator' separates cider-repl from the project name."
88
100
(defvar-local nrepl-repl-buffer nil )
89
101
(defvar-local nrepl-endpoint nil )
90
102
(defvar-local nrepl-project-dir nil )
103
+ (defvar-local nrepl-on-connection-buffer nil )
91
104
92
105
(defconst nrepl-repl-buffer-name-template " *cider-repl%s*" )
93
106
(defconst nrepl-connection-buffer-name-template " *nrepl-connection%s*" )
94
107
(defconst nrepl-server-buffer-name-template " *nrepl-server%s*" )
108
+ (defconst nrepl-on-connection-buffer-name-template " *nrepl-on-connection%s*" )
95
109
96
110
(defcustom nrepl-hide-special-buffers nil
97
111
" 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."
134
148
(nrepl-apply-hide-special-buffers
135
149
(nrepl-buffer-name nrepl-server-buffer-name-template)))
136
150
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
+
137
156
; ; buffer local declarations
138
157
(defvar-local nrepl-session nil
139
158
" Current nREPL session id." )
@@ -463,6 +482,8 @@ Also closes associated REPL and server buffers."
463
482
(when (buffer-live-p buffer)
464
483
(dolist (buf-name `(,(buffer-local-value 'nrepl-repl-buffer buffer)
465
484
,(buffer-local-value 'nrepl-server-buffer buffer)
485
+ ,(buffer-local-value
486
+ 'nrepl-on-connection-buffer buffer)
466
487
, buffer ))
467
488
(when buf-name
468
489
(cider--close-buffer buf-name)))))))
@@ -695,6 +716,26 @@ are processed."
695
716
(nrepl-send-request-sync (nrepl-eval-request input ns session)))
696
717
697
718
; ;; 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
+
698
739
(defun nrepl-server-filter (process output )
699
740
" Process nREPL server output from PROCESS contained in OUTPUT."
700
741
(with-current-buffer (process-buffer process)
@@ -705,15 +746,21 @@ are processed."
705
746
(let ((port (string-to-number (match-string 1 output))))
706
747
(message (format " nREPL server started on %s " port))
707
748
(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))))))))
717
764
718
765
(defun nrepl-server-sentinel (process event )
719
766
" Handle nREPL server PROCESS EVENT."
@@ -735,6 +782,66 @@ are processed."
735
782
(error " Leiningen 2.x is required by CIDER " ))
736
783
(t (error " Could not start nREPL server: %s " problem)))))
737
784
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
+
738
845
(defun nrepl-current-dir ()
739
846
" Return the directory of the current buffer."
740
847
(let ((file-name (buffer-file-name (current-buffer ))))
0 commit comments