@@ -107,11 +107,16 @@ The `nrepl-buffer-name-separator' separates cider-repl from the project name."
107107 :type 'hook
108108 :group 'nrepl )
109109
110- (defcustom nrepl-host " 127.0.0.1 "
110+ (defcustom nrepl-host " localhost "
111111 " The default hostname (or IP address) to connect to."
112112 :type 'string
113113 :group 'nrepl )
114114
115+ (defcustom nrepl-force-ssh-for-remote-hosts nil
116+ " If non-nil, do not attempt a direct connection for remote hosts."
117+ :type 'boolean
118+ :group 'nrepl )
119+
115120(defcustom nrepl-port nil
116121 " The default port to connect to."
117122 :type 'string
@@ -581,10 +586,96 @@ older requests with \"done\" status."
581586 " Handle sentinel events from PROCESS.
582587Display MESSAGE and if the process is closed kill the
583588process buffer and run the hook `nrepl-disconnected-hook' ."
584- (message " nREPL connection closed: %s " message)
589+ (message " nREPL: Connection closed ( %s ) " message)
585590 (if (equal (process-status process) 'closed )
586591 (run-hooks 'nrepl-disconnected-hook )))
587592
593+
594+ ; ;; Network
595+
596+ (defun nrepl-connect (host port )
597+ " Connect to machine identified by HOST and PORT.
598+ For local hosts use a direct connection. For remote hosts, if
599+ `nrepl-force-ssh-for-remote-hosts' is nil, attempt a direct connection
600+ first. If `nrepl-force-ssh-for-remote-hosts' is non-nil or the direct
601+ connection failed, try to start a SSH tunneled connection. Return a plist
602+ of the form (:proc PROC :host \" HOST\" :port PORT) that might contain
603+ additional key-values depending on the connection type."
604+ (let ((localp (if host
605+ (nrepl-local-host-p host)
606+ (not (file-remote-p default-directory)))))
607+ (if localp
608+ (nrepl--direct-connect (or host " localhost" ) port)
609+ (or (and host (not nrepl-force-ssh-for-remote-hosts)
610+ (nrepl--direct-connect host port 'no-error ))
611+ (nrepl--ssh-tunnel-connect host port)))))
612+
613+ (defun nrepl--direct-connect (host port &optional no-error )
614+ " If HOST and PORT are given, try to `open-network-stream' .
615+ If NO-ERROR is non-nil, show messages instead of throwing an error."
616+ (if (not (and host port))
617+ (unless no-error
618+ (error " Host (%s ) and port (%s ) must be provided " host port))
619+ (message " nREPL: Establishing direct connection to %s :%s ... " host port)
620+ (condition-case nil
621+ (prog1 (list :proc (open-network-stream " nrepl" nil host port)
622+ :host host :port port)
623+ (message " nREPL: Direct connection established " ))
624+ (error (let ((mes " nREPL: Direct connection failed" ))
625+ (if no-error (message mes) (error mes))
626+ nil )))))
627+
628+ (defun nrepl--ssh-tunnel-connect (host port )
629+ " Connect to a remote machine identified by HOST and PORT through SSH tunnel."
630+ (message " nREPL: Establishing SSH tunneled connection ... " )
631+ (let* ((remote-dir (if host (format " /ssh:%s : " host) default-directory))
632+ (ssh (or (executable-find " ssh" )
633+ (error " nREPL: Cannot locate 'ssh' executable " )))
634+ (cmd (nrepl--ssh-tunnel-command ssh remote-dir port))
635+ (tunnel-buf (nrepl-tunnel-buffer-name))
636+ (tunnel (start-process-shell-command " nrepl-tunnel" tunnel-buf cmd)))
637+ (process-put tunnel :waiting-for-port t )
638+ (set-process-filter tunnel (nrepl--ssh-tunnel-filter port))
639+ (while (and (process-live-p tunnel)
640+ (process-get tunnel :waiting-for-port ))
641+ (accept-process-output nil 0.005 ))
642+ (if (not (process-live-p tunnel))
643+ (error " nREPL: SSH port forwarding failed. Check the '%s ' buffer. " tunnel-buf)
644+ (message " nREPL: SSH port forwarding established to localhost:%s " port)
645+ (let ((endpoint (nrepl--direct-connect " localhost" port)))
646+ (-> endpoint
647+ (plist-put :tunnel tunnel)
648+ (plist-put :remote-host host))))))
649+
650+ (defun nrepl--ssh-tunnel-command (ssh dir port )
651+ " Command string to open SSH tunnel to the host associated with DIR's PORT."
652+ (with-parsed-tramp-file-name dir nil
653+ ; ; this abuses the -v option for ssh to get output when the port
654+ ; ; forwarding is set up, which is used to synchronise on, so that
655+ ; ; the port forwarding is up when we try to connect.
656+ (format-spec
657+ " %s -v -N -L %p:localhost:%p %u'%h'"
658+ `((?s . , ssh )
659+ (?p . , port )
660+ (?h . , host )
661+ (?u . ,(if user (format " -l '%s ' " user) " " ))))))
662+
663+ (defun nrepl--ssh-tunnel-filter (port )
664+ " Return a process filter that waits for PORT to appear in process output."
665+ (let ((port-string (format " LOCALHOST:%s " port)))
666+ (lambda (proc string )
667+ (when (string-match port-string string)
668+ (process-put proc :waiting-for-port nil ))
669+ (when (and (process-live-p proc)
670+ (buffer-live-p (process-buffer proc)))
671+ (with-current-buffer (process-buffer proc)
672+ (let ((moving (= (point ) (process-mark proc))))
673+ (save-excursion
674+ (goto-char (process-mark proc))
675+ (insert string)
676+ (set-marker (process-mark proc) (point )))
677+ (if moving (goto-char (process-mark proc)))))))))
678+
588679
589680; ;; Client: Process Handling
590681
@@ -598,16 +689,15 @@ If eitehr HOST or PORT are nil, pick them from the value returned by
598689connection which is associated with a repl buffer. When non-nil,
599690SERVER-PROC must be a running nrepl server process within Emacs. Return
600691the newly created client connection process."
601- (let* ((endpoint (nrepl-connection-endpoint host port))
692+ (let* ((endpoint (nrepl-connect host port))
693+ (client-proc (plist-get endpoint :proc ))
602694 (host (plist-get endpoint :host ))
603695 (port (plist-get endpoint :port ))
604- (server-buf (and server-proc (process-buffer server-proc)))
605696 (client-buf (if replp
606697 (cider-repl-create default-directory host port)
607- (nrepl-create-connection-buffer default-directory host port)))
608- (client-proc (open-network-stream " nrepl" client-buf host port))
609- (tunnel-proc (plist-get endpoint :proc ))
610- (nrepl-connection-dispatch client-buf))
698+ (nrepl-create-connection-buffer default-directory host port))))
699+
700+ (set-process-buffer client-proc (get-buffer client-buf))
611701
612702 (set-process-filter client-proc 'nrepl-client-filter )
613703 (set-process-sentinel client-proc 'nrepl-client-sentinel )
@@ -617,15 +707,16 @@ the newly created client connection process."
617707 (process-put client-proc :response-q (nrepl-response-queue))
618708
619709 (with-current-buffer client-buf
620- (when server-buf
710+ (- when-let ( server-buf ( and server-proc ( process-buffer server-proc)))
621711 (setq nrepl-project-dir (buffer-local-value 'nrepl-project-dir server-buf)
622712 nrepl-server-buffer server-buf))
623713 (setq nrepl-endpoint `(, host , port )
624714 ; ; FIXME: REPL and connection buffers are the same thing
625715 nrepl-connection-buffer client-buf
626716 nrepl-repl-buffer (when replp client-buf)
627717 nrepl-buffer-ns " user"
628- nrepl-tunnel-buffer (and tunnel-proc (process-buffer tunnel-proc))
718+ nrepl-tunnel-buffer (-when-let (tunnel (plist-get endpoint :tunnel ))
719+ (process-buffer tunnel))
629720 nrepl-pending-requests (make-hash-table :test 'equal )
630721 nrepl-completed-requests (make-hash-table :test 'equal )))
631722
@@ -901,69 +992,6 @@ Return a newly created process."
901992 (error " Leiningen 2.x is required by CIDER " ))
902993 (t (error " Could not start nREPL server: %s " problem)))))
903994
904- (defun nrepl-connection-endpoint (host port )
905- " Return a connection endpoint.
906- The returned endpoint is a `plist` of the form:
907-
908- (:proc PROCESS :host \" hostname\" :port 1234)
909-
910- If HOST is local :proc will be nil, :host - \" localhost\" and :port - PORT.
911-
912- If HOST is nil and `default-directory' is remote, or if HOST is remote
913- attempt to start an SSH tunnel and return it as :proc slot. If no `ssh'
914- executable has been found, fall back to specifying a direct connection to
915- the remote host."
916- (let ((localp (if host
917- (nrepl-local-host-p host)
918- (not (file-remote-p default-directory)))))
919- (if localp
920- (list :host (or host " localhost" ) :port port :proc nil )
921- (let ((ssh (executable-find " ssh" )))
922- (if ssh
923- ; ; create a tunnel
924- (let* ((remote-dir (if host (format " /ssh:%s : " host) default-directory))
925- (cmd (nrepl--ssh-tunnel-command ssh remote-dir port))
926- (proc (start-process-shell-command
927- " nrepl-tunnel" (nrepl-tunnel-buffer-name) cmd)))
928- (process-put proc :waiting-for-port t )
929- (set-process-filter proc (nrepl--ssh-tunnel-filter port))
930- (while (and (process-live-p proc)
931- (process-get proc :waiting-for-port ))
932- (accept-process-output nil 0.005 ))
933- (unless (process-live-p proc)
934- (message " SSH port forwarding failed " ))
935- (list :host " localhost" :port port :proc proc))
936- ; ; try a direct connection
937- (list :host tramp-current-host :port port :proc nil ))))))
938-
939- (defun nrepl--ssh-tunnel-command (ssh dir port )
940- " Command string to open SSH tunnel to the host associated with DIR's PORT."
941- (with-parsed-tramp-file-name dir nil
942- ; ; this abuses the -v option for ssh to get output when the port
943- ; ; forwarding is set up, which is used to synchronise on, so that
944- ; ; the port forwarding is up when we try to connect.
945- (format-spec
946- " %s -v -N -L %p:localhost:%p %u'%h'"
947- `((?s . , ssh )
948- (?p . , port )
949- (?h . , host )
950- (?u . ,(if user (format " -l '%s ' " user) " " ))))))
951-
952- (defun nrepl--ssh-tunnel-filter (port )
953- " Return a filter function for waiting on PORT to appear in output."
954- (let ((port-string (format " LOCALHOST:%s " port)))
955- (lambda (proc string )
956- (when (string-match port-string string)
957- (process-put proc :waiting-for-port nil ))
958- (when (buffer-live-p (process-buffer proc))
959- (with-current-buffer (process-buffer proc)
960- (let ((moving (= (point ) (process-mark proc))))
961- (save-excursion
962- (goto-char (process-mark proc))
963- (insert string)
964- (set-marker (process-mark proc) (point )))
965- (if moving (goto-char (process-mark proc)))))))))
966-
967995
968996; ;; Messages
969997
@@ -1040,10 +1068,6 @@ The default buffer name is *nrepl-messages*."
10401068
10411069; ;; Connection Buffer Management
10421070
1043- (defvar nrepl-connection-dispatch nil
1044- " Bound to the connection a message was received on.
1045- This is bound for the duration of the handling of that message" )
1046-
10471071(defvar nrepl-connection-list nil
10481072 " A list of connections." )
10491073
@@ -1067,8 +1091,7 @@ PROJECT-DIR, HOST and PORT are as in `nrepl-make-buffer-name'."
10671091 " The connection to use for nREPL interaction.
10681092When NO-ERROR is non-nil, don't throw an error when no connection has been
10691093found."
1070- (or nrepl-connection-dispatch
1071- nrepl-connection-buffer
1094+ (or nrepl-connection-buffer
10721095 (car (nrepl-connection-buffers))
10731096 (unless no-error
10741097 (error " No nREPL connection buffer " ))))
@@ -1098,16 +1121,15 @@ Moves CONNECITON-BUFFER to the front of `nrepl-connection-list'."
10981121(defun nrepl--close-connection-buffer (conn-buffer )
10991122 " Closes CONN-BUFFER, removing it from `nrepl-connection-list' .
11001123Also closes associated REPL and server buffers."
1101- (let ((nrepl-connection-dispatch conn-buffer))
1102- (let ((buffer (get-buffer conn-buffer)))
1103- (setq nrepl-connection-list
1104- (delq (buffer-name buffer) nrepl-connection-list))
1105- (when (buffer-live-p buffer)
1106- (dolist (buf `(,(buffer-local-value 'nrepl-server-buffer buffer)
1107- ,(buffer-local-value 'nrepl-tunnel-buffer buffer)
1108- , buffer ))
1109- (when buf
1110- (cider--close-buffer buf)))))))
1124+ (let ((buffer (get-buffer conn-buffer)))
1125+ (setq nrepl-connection-list
1126+ (delq (buffer-name buffer) nrepl-connection-list))
1127+ (when (buffer-live-p buffer)
1128+ (dolist (buf `(,(buffer-local-value 'nrepl-server-buffer buffer)
1129+ ,(buffer-local-value 'nrepl-tunnel-buffer buffer)
1130+ , buffer ))
1131+ (when buf
1132+ (cider--close-buffer buf))))))
11111133
11121134
11131135; ;; Connection Browser
0 commit comments