Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#955] Reuse existing repl buffer when cleint process has died #958

Merged
merged 2 commits into from
Mar 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions cider-repl.el
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,25 @@ 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-use-this-as-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 reusing, rename the buffer accordingly
(when (and reuse-buff
(not (equal buff-name nrepl-use-this-as-repl-buffer)))
;; uniquify as it might be Nth connection to the same endpoint
(setq buff-name (generate-new-buffer-name buff-name))
(with-current-buffer nrepl-use-this-as-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-use-this-as-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-use-this-as-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
100 changes: 67 additions & 33 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-use-this-as-repl-buffer 'new
"Name of the buffer to use as REPL buffer.
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few unit tests for this new behaviour would be useful.

"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 process is alive, ask the user for
confirmation and return 'new/nil for y/n answer respectively. If other
REPL buffers with dead process 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-buffer-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 "Zombi REPL buffers 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-buffer-or-new))
exact-buff)
(zombi-buffer-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-use-this-as-repl-buffer
nrepl-use-this-as-repl-buffer))
(message "Starting nREPL server via %s..."
(propertize cmd 'face 'font-lock-keyword-face))
serv-proc))
Expand Down Expand Up @@ -1187,13 +1214,20 @@ found."
(error "No nREPL connection buffer"))))

(defun nrepl-connection-buffers ()
"Return the connection list.
Purge the dead buffers from the `nrepl-connection-list' beforehand."
"Return the list of connection buffers."
(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