Skip to content

Commit

Permalink
[#955] Reuse existing repl buffer when cleint process has died
Browse files Browse the repository at this point in the history
  • Loading branch information
vspinu committed Mar 18, 2015
1 parent 59671a7 commit 6869a4b
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 44 deletions.
18 changes: 13 additions & 5 deletions cider-repl.el
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,24 @@ PROJECT-DIR, PORT and HOST are as in `nrepl-make-buffer-name'."
"Create a REPL buffer and install `cider-repl-mode'.
ENDPOINT is a plist as returned by `nrepl-connect'."
;; Connection might not have been set as yet. Please don't send requests here.
(let ((buf (nrepl-make-buffer-name nrepl-repl-buffer-name-template nil
(plist-get endpoint :host)
(plist-get endpoint :port))))
(with-current-buffer (get-buffer-create buf)
(let* ((reuse-buff (not (eq 'new nrepl-reuse-this-repl-buffer)))
(buff-name (nrepl-make-buffer-name nrepl-repl-buffer-name-template nil
(plist-get endpoint :host)
(plist-get endpoint :port)
reuse-buff)))
(when (and reuse-buff
(not (equal buff-name nrepl-reuse-this-repl-buffer)))
;; still need to uniquify as it might be a connection to the endpoint
(setq buff-name (generate-new-buffer-name buff-name))
(with-current-buffer nrepl-reuse-this-repl-buffer
(rename-buffer buff-name)))
(with-current-buffer (get-buffer-create buff-name)
(unless (derived-mode-p 'cider-repl-mode)
(cider-repl-mode))
(cider-repl-reset-markers)
(add-hook 'nrepl-connected-hook 'cider--connected-handler nil 'local)
(add-hook 'nrepl-disconnected-hook 'cider--disconnected-handler nil 'local))
buf))
buff-name))

(defun cider-repl-require-repl-utils ()
"Require standard REPL util functions into the current REPL."
Expand Down
16 changes: 9 additions & 7 deletions cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ start the server."
(setq cider-current-clojure-buffer (current-buffer))
(let ((project-type (or (cider-project-type) cider-default-repl-command)))
(if (funcall (cider-command-present-p project-type))
(let* ((nrepl-create-client-buffer-function #'cider-repl-create)
(project (when prompt-project
(let* ((project (when prompt-project
(read-directory-name "Project: ")))
(project-dir (nrepl-project-directory-for
(or project (nrepl-current-dir))))
Expand All @@ -183,8 +182,10 @@ start the server."
(cider-jack-in-params project-type))
(cider-jack-in-params project-type)))
(cmd (format "%s %s" (cider-jack-in-command project-type) params)))
(when (nrepl-check-for-repl-buffer nil project-dir)
(nrepl-start-server-process project-dir cmd)))
(-when-let (repl-buff (nrepl-find-reusable-repl-buffer nil project-dir))
(let ((nrepl-create-client-buffer-function #'cider-repl-create)
(nrepl-reuse-this-repl-buffer repl-buff))
(nrepl-start-server-process project-dir cmd))))
(message "The %s executable (specified by `cider-lein-command' or `cider-boot-command') isn't on your exec-path"
(cider-jack-in-command project-type)))))

Expand All @@ -194,9 +195,10 @@ start the server."
Create REPL buffer and start an nREPL client connection."
(interactive (cider-select-endpoint))
(setq cider-current-clojure-buffer (current-buffer))
(when (nrepl-check-for-repl-buffer `(,host ,port) nil)
(let ((nrepl-create-client-buffer-function #'cider-repl-create))
(nrepl-start-client-process host port))))
(-when-let (repl-buff (nrepl-find-reusable-repl-buffer `(,host ,port) nil))
(let ((nrepl-create-client-buffer-function #'cider-repl-create)
(nrepl-reuse-this-repl-buffer repl-buff))
(nrepl-start-client-process host port))))

(defun cider-select-endpoint ()
"Interactively select the host and port to connect to."
Expand Down
99 changes: 67 additions & 32 deletions nrepl-client.el
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ buffer will be hidden."
It is called with one argument, a plist containing :host, :port and :proc
as returned by `nrepl-connect'. ")

(defvar nrepl-reuse-this-repl-buffer 'new
"Name of the buffer to reuse (if any) on REPL creation.
In case of a special value 'new, a new buffer is created.")


;;; nREPL Buffer Names

Expand All @@ -156,23 +160,28 @@ as returned by `nrepl-connect'. ")
(concat nrepl-buffer-name-separator designation)
"")))

(defun nrepl-make-buffer-name (buffer-name-template &optional project-dir host port)
(defun nrepl-make-buffer-name (buffer-name-template &optional project-dir host port dup-ok)
"Generate a buffer name using BUFFER-NAME-TEMPLATE.
If not supplied PROJECT-DIR, PORT and HOST default to the buffer local
value of the `nrepl-project-dir' and `nrepl-endpoint'.
The name will include the project name if available or the endpoint host if
it is not. The name will also include the connection port if
`nrepl-buffer-name-show-port' is true."
(generate-new-buffer-name
(let ((project-name (nrepl--project-name (or project-dir nrepl-project-dir)))
(nrepl-proj-port (or port (cadr nrepl-endpoint))))
(nrepl-format-buffer-name-template
buffer-name-template
(concat (if project-name project-name (or host (car nrepl-endpoint)))
(if (and nrepl-proj-port nrepl-buffer-name-show-port)
(format ":%s" nrepl-proj-port) ""))))))
`nrepl-buffer-name-show-port' is true.
If optional DUP-OK is non-nil, the returned buffer is not \"uniquefied\" by
`generate-new-buffer-name'."
(let* ((project-name (nrepl--project-name (or project-dir nrepl-project-dir)))
(nrepl-proj-port (or port (cadr nrepl-endpoint)))
(name (nrepl-format-buffer-name-template
buffer-name-template
(concat (if project-name project-name (or host (car nrepl-endpoint)))
(if (and nrepl-proj-port nrepl-buffer-name-show-port)
(format ":%s" nrepl-proj-port) "")))))
(if dup-ok
name
(generate-new-buffer-name name))))

(defun nrepl--make-hidden-name (buffer-name)
"Apply a prefix to BUFFER-NAME that will hide the buffer."
Expand Down Expand Up @@ -267,25 +276,41 @@ Bind the value of the provided KEYS and execute BODY."
(or (locate-dominating-file dir-name "project.clj")
(locate-dominating-file dir-name "build.boot"))))

(defun nrepl-check-for-repl-buffer (endpoint project-directory)
"Check whether a matching connection buffer already exists.
Looks for buffers where `nrepl-endpoint' matches ENDPOINT,
or `nrepl-project-dir' matches PROJECT-DIRECTORY.
If so ask the user for confirmation."
(if (cl-find-if
(lambda (buffer)
(let ((buffer (get-buffer buffer)))
(or (and endpoint
(equal endpoint
(buffer-local-value 'nrepl-endpoint buffer)))
(and project-directory
(equal project-directory
(buffer-local-value 'nrepl-project-dir buffer))))))
(nrepl-connection-buffers))
(y-or-n-p
"An nREPL connection buffer already exists. Do you really want to create a new one? ")
t))

(defun nrepl-find-reusable-repl-buffer (endpoint project-directory)
"Check whether a reusable connection buffer already exists.
Looks for buffers where `nrepl-endpoint' matches ENDPOINT, or
`nrepl-project-dir' matches PROJECT-DIRECTORY. If such a buffer was found,
and has no process, return it. If the buffer process is alive, ask the
user for confirmation and return 'new/nil for y/n answer respectively. If
other REPL buffers with dead processes exist, ask the user if any of those
should be reused."
(let* ((repl-buffs (-map #'buffer-name (nrepl-repl-buffers)))
(exact-buff (-first (lambda (buff)
(with-current-buffer buff
(or (and endpoint (equal endpoint nrepl-endpoint))
(and project-directory (equal project-directory nrepl-project-dir)))))
repl-buffs)))
(cl-flet ((zombi-buff-or-new
() (let ((zombi-buffs (-remove (lambda (buff)
(process-live-p (get-buffer-process buff)))
repl-buffs)))
(if zombi-buffs
(if (y-or-n-p (format "REPL buffers with no connection exist (%s). Reuse? "
(cider-string-join zombi-buffs ", ")))
(if (= (length zombi-buffs) 1)
(car zombi-buffs)
(completing-read "Choose REPL buffer: " zombi-buffs nil t))
'new)
'new))))
(if exact-buff
(if (process-live-p (get-buffer-process exact-buff))
(when (y-or-n-p
(format "REPL buffer already exists (%s). Do you really want to create a new one? "
exact-buff))
(zombi-buff-or-new))
exact-buff)
(zombi-buff-or-new)))))

(defun nrepl-extract-port (&optional dir)
"Read port from .nrepl-port, nrepl-port or target/repl-port files in directory DIR."
(-when-let (dir (or dir (nrepl-project-directory-for (nrepl-current-dir))))
Expand Down Expand Up @@ -1018,9 +1043,11 @@ Return a newly created process."
(set-process-coding-system serv-proc 'utf-8-unix 'utf-8-unix)
(with-current-buffer serv-buf
(setq nrepl-project-dir directory)
;; ensure that `nrepl-start-client-process' sees right function
;; ensure that `nrepl-start-client-process' sees right things:
(setq-local nrepl-create-client-buffer-function
nrepl-create-client-buffer-function))
nrepl-create-client-buffer-function)
(setq-local nrepl-reuse-this-repl-buffer
nrepl-reuse-this-repl-buffer))
(message "Starting nREPL server via %s..."
(propertize cmd 'face 'font-lock-keyword-face))
serv-proc))
Expand Down Expand Up @@ -1187,13 +1214,21 @@ found."
(error "No nREPL connection buffer"))))

(defun nrepl-connection-buffers ()
"Return the connection list.
"Return the list of connection buffers.
Purge the dead buffers from the `nrepl-connection-list' beforehand."
(setq nrepl-connection-list
(-remove (lambda (buffer)
(not (buffer-live-p (get-buffer buffer))))
nrepl-connection-list)))

(defun nrepl-repl-buffers ()
"Return the list of repl buffers.
Purge the dead buffers from the `nrepl-connection-list' beforehand."
(-filter
(lambda (buffer)
(with-current-buffer buffer (derived-mode-p 'cider-repl-mode)))
(buffer-list)))

;; FIXME: Bad user api; don't burden users with management of
;; the connection list, same holds for `cider-rotate-connection'.
(defun nrepl-make-connection-default (connection-buffer)
Expand Down

0 comments on commit 6869a4b

Please sign in to comment.