summaryrefslogtreecommitdiff
path: root/nrepl-client.el
diff options
context:
space:
mode:
authorBozhidar Batsov <bozhidar@tradeo.com>2013-10-11 15:37:27 +0300
committerBozhidar Batsov <bozhidar@tradeo.com>2013-10-11 15:37:27 +0300
commit89174492f4cbeff6f85bf72f0312fd8ff1a53a12 (patch)
tree154f00826a1cf4f32cffdbe420704beb24552ea2 /nrepl-client.el
parent38c569081eaa25d94d7e3dcd7b2942aa88ae8109 (diff)
Start to break up the code into separate components
Diffstat (limited to 'nrepl-client.el')
-rw-r--r--nrepl-client.el2460
1 files changed, 2460 insertions, 0 deletions
diff --git a/nrepl-client.el b/nrepl-client.el
new file mode 100644
index 00000000..cb50406c
--- /dev/null
+++ b/nrepl-client.el
@@ -0,0 +1,2460 @@
+;;; nrepl-client.el --- Client for Clojure nREPL
+
+;; Copyright © 2012-2013 Tim King, Phil Hagelberg
+;; Copyright © 2013 Bozhidar Batsov, Hugo Duncan, Steve Purcell
+;;
+;; Author: Tim King <kingtim@gmail.com>
+;; Phil Hagelberg <technomancy@gmail.com>
+;; Bozhidar Batsov <bozhidar@batsov.com>
+;; Hugo Duncan <hugo@hugoduncan.org>
+;; Steve Purcell <steve@sanityinc.com>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;; This file is not part of GNU Emacs.
+
+;;; Commentary:
+
+;; Provides an Emacs Lisp client to connect to Clojure nREPL servers.
+
+;;; Code:
+
+(require 'clojure-mode)
+(require 'dash)
+(require 'pkg-info)
+(require 'thingatpt)
+(require 'etags)
+(require 'arc-mode)
+(require 'ansi-color)
+(require 'eldoc)
+(require 'ewoc)
+(require 'cl-lib)
+(require 'compile)
+(require 'tramp)
+
+(eval-when-compile
+ (defvar paredit-version)
+ (defvar paredit-space-for-delimiter-predicates))
+
+
+;;; Compatibility
+(eval-and-compile
+ ;; `setq-local' for Emacs 24.2 and below
+ (unless (fboundp 'setq-local)
+ (defmacro setq-local (var val)
+ "Set variable VAR to value VAL in current buffer."
+ `(set (make-local-variable ',var) ,val))))
+
+
+(defgroup nrepl nil
+ "Interaction with the Clojure nREPL Server."
+ :prefix "nrepl-"
+ :group 'applications)
+
+
+;;; Version information
+(defun nrepl-library-version ()
+ "Get the version in the nrepl library header."
+ (-when-let (version (pkg-info-defining-library-version 'nrepl-repl-mode))
+ (pkg-info-format-version version)))
+
+(defun nrepl-package-version ()
+ "Get the package version of nrepl.
+
+This is the version number of the installed nrepl package."
+ (-when-let (version (pkg-info-package-version 'nrepl))
+ (pkg-info-format-version version)))
+
+(defun nrepl-version (&optional show-version)
+ "Get the nrepl version as string.
+
+If called interactively or if SHOW-VERSION is non-nil, show the
+version in the echo area and the messages buffer.
+
+The returned string includes both, the version from package.el
+and the library version, if both a present and different.
+
+If the version number could not be determined, signal an error,
+if called interactively, or if SHOW-VERSION is non-nil, otherwise
+just return nil."
+ (interactive (list (not (or executing-kbd-macro noninteractive))))
+ (let* ((lib-version (nrepl-library-version))
+ (pkg-version (nrepl-package-version))
+ (version (cond
+ ((and lib-version pkg-version
+ (not (string= lib-version pkg-version)))
+ (format "%s (package: %s)" lib-version pkg-version))
+ ((or pkg-version lib-version)
+ (format "%s" (or pkg-version lib-version))))))
+ (when show-version
+ (unless version
+ (error "Could not find out nrepl version"))
+ (message "nrepl version: %s" version))
+ version))
+
+(defcustom nrepl-connected-hook nil
+ "List of functions to call when connecting to the nREPL server."
+ :type 'hook
+ :group 'nrepl)
+
+(defcustom nrepl-disconnected-hook nil
+ "List of functions to call when disconnected from the nREPL server."
+ :type 'hook
+ :group 'nrepl)
+
+(defcustom nrepl-file-loaded-hook nil
+ "List of functions to call when a load file has completed."
+ :type 'hook
+ :group 'nrepl)
+
+(defcustom nrepl-host "127.0.0.1"
+ "The default hostname (or IP address) to connect to."
+ :type 'string
+ :group 'nrepl)
+
+(defcustom nrepl-port nil
+ "The default port to connect to."
+ :type 'string
+ :group 'nrepl)
+
+(defvar nrepl-repl-requires-sexp "(apply require '[[clojure.repl :refer (source apropos dir pst doc find-doc)] [clojure.java.javadoc :refer (javadoc)] [clojure.pprint :refer (pp pprint)]])"
+ "Things to require in the tooling session and the REPL buffer.")
+
+(defvar nrepl-connection-buffer nil)
+(defvar nrepl-server-buffer nil)
+(defvar nrepl-repl-buffer nil)
+(defvar nrepl-endpoint nil)
+(defvar nrepl-project-dir nil)
+(defconst nrepl-error-buffer "*nrepl-error*")
+(defconst nrepl-doc-buffer "*nrepl-doc*")
+(defconst nrepl-src-buffer "*nrepl-src*")
+(defconst nrepl-macroexpansion-buffer "*nrepl-macroexpansion*")
+(defconst nrepl-result-buffer "*nrepl-result*")
+(defconst nrepl-repl-buffer-name-template "*nrepl%s*")
+(defconst nrepl-connection-buffer-name-template "*nrepl-connection%s*")
+(defconst nrepl-server-buffer-name-template "*nrepl-server%s*")
+
+(defcustom nrepl-hide-special-buffers nil
+ "Control the display of some special buffers in buffer switching commands.
+When true some special buffers like the connection and the server
+buffer will be hidden.")
+
+(defun nrepl-apply-hide-special-buffers (buffer-name)
+ "Apply a prefix to BUFFER-NAME that will hide the buffer."
+ (concat (if nrepl-hide-special-buffers " " "") buffer-name))
+
+(defun nrepl-buffer-name (buffer-name-template)
+ "Generate a buffer name using BUFFER-NAME-TEMPLATE.
+
+The name will include the project name if available. 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 nrepl-project-dir))
+ (nrepl-proj-port (cadr nrepl-endpoint)))
+ (format
+ buffer-name-template
+ (concat (if project-name
+ (format "%s%s" nrepl-buffer-name-separator project-name) "")
+ (if (and nrepl-proj-port nrepl-buffer-name-show-port)
+ (format ":%s" nrepl-proj-port) ""))))))
+
+(defun nrepl-connection-buffer-name ()
+ "Return the name of the connection buffer."
+ (nrepl-apply-hide-special-buffers
+ (nrepl-buffer-name nrepl-connection-buffer-name-template)))
+
+(defun nrepl-server-buffer-name ()
+ "Return the name of the server buffer."
+ (nrepl-apply-hide-special-buffers
+ (nrepl-buffer-name nrepl-server-buffer-name-template)))
+
+
+(defface nrepl-error-highlight-face
+ '((((supports :underline (:style wave)))
+ (:underline (:style wave :color "red") :inherit unspecified))
+ (t (:inherit font-lock-warning-face :underline t)))
+ "Face used to highlight compilation errors in Clojure buffers."
+ :group 'nrepl)
+
+(defface nrepl-warning-highlight-face
+ '((((supports :underline (:style wave)))
+ (:underline (:style wave :color "yellow") :inherit unspecified))
+ (t (:inherit font-lock-warning-face :underline (:color "yellow"))))
+ "Face used to highlight compilation warnings in Clojure buffers."
+ :group 'nrepl)
+
+;; buffer local declarations
+(defvar nrepl-session nil
+ "Current nREPL session id.")
+
+(defvar nrepl-tooling-session nil
+ "Current nREPL tooling session id.
+To be used for tooling calls (i.e. completion, eldoc, etc)")
+
+(defvar nrepl-input-start-mark)
+
+(defvar nrepl-prompt-start-mark)
+
+(defvar nrepl-request-counter 0
+ "Continuation serial number counter.")
+
+(defvar nrepl-old-input-counter 0
+ "Counter used to generate unique `nrepl-old-input' properties.
+This property value must be unique to avoid having adjacent inputs be
+joined together.")
+
+(defvar nrepl-requests (make-hash-table :test 'equal))
+
+(defvar nrepl-buffer-ns "user"
+ "Current Clojure namespace of this buffer.")
+
+(defvar nrepl-input-history '()
+ "History list of strings read from the nREPL buffer.")
+
+(defvar nrepl-input-history-items-added 0
+ "Variable counting the items added in the current session.")
+
+(defvar nrepl-output-start nil
+ "Marker for the start of output.")
+
+(defvar nrepl-output-end nil
+ "Marker for the end of output.")
+
+(defvar nrepl-sync-response nil
+ "Result of the last sync request.")
+
+(defvar nrepl-err-handler 'nrepl-default-err-handler
+ "Evaluation error handler.")
+
+(defvar nrepl-extra-eldoc-commands '("nrepl-complete" "yas/expand")
+ "Extra commands to be added to eldoc's safe commands list.")
+
+(defvar nrepl-ops nil
+ "Available nREPL server ops (from describe).")
+
+(defcustom nrepl-popup-stacktraces t
+ "Non-nil means pop-up error stacktraces for evaluation errors.
+Nil means show only an error message in the minibuffer. See also
+`nrepl-popup-stacktraces-in-repl', which overrides this setting
+for REPL buffers."
+ :type 'boolean
+ :group 'nrepl)
+
+(defcustom nrepl-popup-on-error t
+ "When `nrepl-popup-on-error' is set to t, stacktraces will be displayed.
+When set to nil, stactraces will not be displayed, but will be available
+in the `nrepl-error-buffer', which defaults to *nrepl-error*."
+ :type 'boolean
+ :group 'nrepl)
+
+(defcustom nrepl-auto-select-error-buffer nil
+ "Controls whether to auto-select the error popup buffer."
+ :type 'boolean
+ :group 'nrepl)
+
+(defcustom nrepl-buffer-name-separator " "
+ "Used in constructing the REPL buffer name.
+The `nrepl-buffer-name-separator' separates `nrepl' from the project name."
+ :type '(string)
+ :group 'nrepl)
+
+(defcustom nrepl-buffer-name-show-port nil
+ "Show the connection port in the nrepl REPL buffer name, if set to t."
+ :type 'boolean
+ :group 'nrepl)
+
+(defun nrepl-make-variables-buffer-local (&rest variables)
+ "Make all VARIABLES buffer local."
+ (mapcar #'make-variable-buffer-local variables))
+
+(nrepl-make-variables-buffer-local
+ 'nrepl-connection-buffer
+ 'nrepl-repl-buffer
+ 'nrepl-server-buffer
+ 'nrepl-endpoint
+ 'nrepl-project-dir
+ 'nrepl-ops
+ 'nrepl-session
+ 'nrepl-tooling-session
+ 'nrepl-input-start-mark
+ 'nrepl-prompt-start-mark
+ 'nrepl-request-counter
+ 'nrepl-requests
+ 'nrepl-old-input-counter
+ 'nrepl-buffer-ns
+ 'nrepl-input-history
+ 'nrepl-input-history-items-added
+ 'nrepl-current-input-history-index
+ 'nrepl-output-start
+ 'nrepl-output-end
+ 'nrepl-sync-response)
+
+;;; Bencode
+;;; Adapted from http://www.emacswiki.org/emacs-en/bencode.el
+;;; and modified to work with utf-8
+(defun nrepl-bdecode-buffer ()
+ "Decode a bencoded string in the current buffer starting at point."
+ (cond ((looking-at "i\\([0-9]+\\)e")
+ (goto-char (match-end 0))
+ (string-to-number (match-string 1)))
+ ((looking-at "\\([0-9]+\\):")
+ (goto-char (match-end 0))
+ (let ((start (point))
+ (end (byte-to-position (+ (position-bytes (point))
+ (string-to-number (match-string 1))))))
+ (goto-char end)
+ (buffer-substring-no-properties start end)))
+ ((looking-at "l")
+ (goto-char (match-end 0))
+ (let (result item)
+ (while (setq item (nrepl-bdecode-buffer))
+ (setq result (cons item result)))
+ (nreverse result)))
+ ((looking-at "d")
+ (goto-char (match-end 0))
+ (let (dict key item)
+ (while (setq item (nrepl-bdecode-buffer))
+ (if key
+ (setq dict (cons (cons key item) dict)
+ key nil)
+ (unless (stringp item)
+ (error "Dictionary keys have to be strings: %s" item))
+ (setq key item)))
+ (cons 'dict (nreverse dict))))
+ ((looking-at "e")
+ (goto-char (match-end 0))
+ nil)
+ (t
+ (error "Cannot decode object: %d" (point)))))
+
+(defun nrepl-decode (str)
+ "Decode bencoded STR."
+ (with-temp-buffer
+ (save-excursion
+ (insert str))
+ (let ((result '()))
+ (while (not (eobp))
+ (setq result (cons (nrepl-bdecode-buffer) result)))
+ (nreverse result))))
+
+(defun nrepl-netstring (string)
+ "Encode STRING in bencode."
+ (let ((size (string-bytes string)))
+ (format "%s:%s" size string)))
+
+(defun nrepl-bencode (message)
+ "Encode with bencode MESSAGE."
+ (concat "d" (apply 'concat (mapcar 'nrepl-netstring message)) "e"))
+
+(defun nrepl-eval-region (start end)
+ "Evaluate the region.
+The two arguments START and END are character positions;
+they can be in either order."
+ (interactive "r")
+ (nrepl-interactive-eval (buffer-substring-no-properties start end)))
+
+(defun nrepl-eval-buffer ()
+ "Evaluate the current buffer."
+ (interactive)
+ (nrepl-eval-region (point-min) (point-max)))
+
+(defun nrepl-expression-at-point ()
+ "Return the text of the expr at point."
+ (apply #'buffer-substring-no-properties
+ (nrepl-region-for-expression-at-point)))
+
+(defun nrepl-region-for-expression-at-point ()
+ "Return the start and end position of defun at point."
+ (save-excursion
+ (save-match-data
+ (end-of-defun)
+ (let ((end (point)))
+ (beginning-of-defun)
+ (list (point) end)))))
+
+(defun nrepl-eval-expression-at-point (&optional prefix)
+ "Evaluate the current toplevel form, and print result in the mini-buffer.
+With a PREFIX argument, print the result in the current buffer."
+ (interactive "P")
+ (let ((form (nrepl-expression-at-point)))
+ (if prefix
+ (nrepl-interactive-eval-print form)
+ (nrepl-interactive-eval form))))
+
+(defun nrepl-eval-ns-form ()
+ "Evaluate the current buffer's namespace form."
+ (interactive)
+ (when (clojure-find-ns)
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (nrepl-eval-expression-at-point))))
+
+(defun nrepl-bounds-of-sexp-at-point ()
+ "Return the bounds sexp at point as a pair (or nil)."
+ (or (and (equal (char-after) ?\()
+ (member (char-before) '(?\' ?\, ?\@))
+ ;; hide stuff before ( to avoid quirks with '( etc.
+ (save-restriction
+ (narrow-to-region (point) (point-max))
+ (bounds-of-thing-at-point 'sexp)))
+ (bounds-of-thing-at-point 'sexp)))
+
+(defun nrepl-sexp-at-point ()
+ "Return the sexp at point as a string, otherwise nil."
+ (let ((bounds (nrepl-bounds-of-sexp-at-point)))
+ (if bounds
+ (buffer-substring-no-properties (car bounds)
+ (cdr bounds)))))
+
+(defun nrepl-sexp-at-point-with-bounds ()
+ "Return a list containing the sexp at point and its bounds."
+ (let ((bounds (nrepl-bounds-of-sexp-at-point)))
+ (if bounds
+ (let ((start (car bounds))
+ (end (cdr bounds)))
+ (list (buffer-substring-no-properties start end)
+ (cons (set-marker (make-marker) start)
+ (set-marker (make-marker) end)))))))
+
+(defun nrepl-last-expression ()
+ "Return the last sexp."
+ (buffer-substring-no-properties
+ (save-excursion (backward-sexp) (point))
+ (point)))
+
+(defcustom nrepl-use-local-resources t
+ "Use local resources under HOME if possible."
+ :type 'boolean
+ :group 'nrepl)
+
+(defun nrepl-tramp-prefix ()
+ "Top element on `find-tag-marker-ring` used to determine Clojure host."
+ (let ((jump-origin (buffer-file-name
+ (marker-buffer
+ (ring-ref find-tag-marker-ring 0)))))
+ (when (tramp-tramp-file-p jump-origin)
+ (let ((vec (tramp-dissect-file-name jump-origin)))
+ (tramp-make-tramp-file-name (tramp-file-name-method vec)
+ (tramp-file-name-user vec)
+ (tramp-file-name-host vec)
+ nil)))))
+
+(defun nrepl-home-prefix-adjustment (resource)
+ "System-dependent HOME location will be adjusted in RESOURCE.
+Removes any leading slash if on Windows."
+ (save-match-data
+ (cond ((string-match "^\\/\\(Users\\|home\\)\\/\\w+\\(\\/.+\\)" resource)
+ (concat (getenv "HOME") (match-string 2 resource)))
+ ((and (eq system-type 'windows-nt)
+ (string-match "^/" resource)
+ (not (tramp-tramp-file-p resource)))
+ (substring resource 1))
+ (t
+ resource))))
+
+(defun nrepl-emacs-or-clojure-side-adjustment (resource)
+ "Fix the RESOURCE path depending on `nrepl-use-local-resources`."
+ (let ((resource (nrepl-home-prefix-adjustment resource))
+ (clojure-side-res (concat (nrepl-tramp-prefix) resource))
+ (emacs-side-res resource))
+ (cond ((equal resource "") resource)
+ ((and nrepl-use-local-resources
+ (file-exists-p emacs-side-res))
+ emacs-side-res)
+ ((file-exists-p clojure-side-res)
+ clojure-side-res)
+ (t
+ resource))))
+
+(defun nrepl-find-file (filename)
+ "Switch to a buffer visiting FILENAME.
+Adjusts for HOME location using `nrepl-home-prefix-adjustment'. Uses `find-file'."
+ (find-file (nrepl-emacs-or-clojure-side-adjustment filename)))
+
+(defun nrepl-find-resource (resource)
+ "Find and display RESOURCE."
+ (cond ((string-match "^file:\\(.+\\)" resource)
+ (nrepl-find-file (match-string 1 resource)))
+ ((string-match "^\\(jar\\|zip\\):file:\\(.+\\)!/\\(.+\\)" resource)
+ (let* ((jar (match-string 2 resource))
+ (path (match-string 3 resource))
+ (buffer-already-open (get-buffer (file-name-nondirectory jar))))
+ (nrepl-find-file jar)
+ (goto-char (point-min))
+ (search-forward path)
+ (let ((opened-buffer (current-buffer)))
+ (archive-extract)
+ (when (not buffer-already-open)
+ (kill-buffer opened-buffer)))))
+ (t (error "Unknown resource path %s" resource))))
+
+(defun nrepl-jump-to-def-for (location)
+ "Jump to LOCATION's definition in the source code."
+ ;; ugh; elisp destructuring doesn't work for vectors
+ (let ((resource (aref location 0))
+ (path (aref location 1))
+ (line (aref location 2)))
+ (if (and path (file-exists-p path))
+ (find-file path)
+ (nrepl-find-resource resource))
+ (goto-char (point-min))
+ (forward-line (1- line))))
+
+(defun nrepl-jump-to-def-handler (buffer)
+ "Create a handler for jump-to-def in BUFFER."
+ ;; TODO: got to be a simpler way to do this
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (with-current-buffer buffer
+ (ring-insert find-tag-marker-ring (point-marker)))
+ (nrepl-jump-to-def-for
+ (car (read-from-string value))))
+ (lambda (buffer out) (message out))
+ (lambda (buffer err) (message err))
+ nil))
+
+(defun nrepl-jump-to-def (var)
+ "Jump to the definition of the VAR at point."
+ (let ((form (format "(let [ns-symbol '%s
+ ns-var '%s
+ ns-file (clojure.core/comp :file
+ clojure.core/meta
+ clojure.core/second
+ clojure.core/first
+ clojure.core/ns-publics)
+ resource-str (clojure.core/comp clojure.core/str
+ clojure.java.io/resource
+ ns-file)
+ file-str (clojure.core/comp clojure.core/str
+ clojure.java.io/file
+ ns-file)]
+ (cond ((clojure.core/ns-aliases ns-symbol) ns-var)
+ (let [resolved-ns ((clojure.core/ns-aliases ns-symbol) ns-var)]
+ [(resource-str resolved-ns)
+ (file-str resolved-ns)
+ 1])
+
+ (find-ns ns-var)
+ [(resource-str ns-var)
+ (file-str ns-var)
+ 1]
+
+ (clojure.core/ns-resolve ns-symbol ns-var)
+ ((clojure.core/juxt
+ (clojure.core/comp clojure.core/str
+ clojure.java.io/resource
+ :file)
+ (clojure.core/comp clojure.core/str
+ clojure.java.io/file
+ :file)
+ :line)
+ (clojure.core/meta (clojure.core/ns-resolve ns-symbol ns-var)))))"
+ (nrepl-current-ns) var)))
+ (nrepl-send-string form
+ (nrepl-jump-to-def-handler (current-buffer))
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+
+(defun nrepl-jump (query)
+ "Jump to the definition of QUERY."
+ (interactive "P")
+ (nrepl-read-symbol-name "Symbol: " 'nrepl-jump-to-def query))
+
+(defalias 'nrepl-jump-back 'pop-tag-mark)
+
+(defun nrepl-completion-complete-core-fn (str)
+ "Return a list of completions for STR using complete.core/completions."
+ (let ((strlst (plist-get
+ (nrepl-send-string-sync
+ (format "(require 'complete.core) (complete.core/completions \"%s\" *ns*)" str)
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))
+ :value)))
+ (when strlst
+ (car (read-from-string strlst)))))
+
+(defun nrepl-completion-complete-op-fn (str)
+ "Return a list of completions for STR using the nREPL \"complete\" op."
+ (lexical-let ((strlst (plist-get
+ (nrepl-send-request-sync
+ (list "op" "complete"
+ "session" (nrepl-current-tooling-session)
+ "ns" nrepl-buffer-ns
+ "symbol" str))
+ :value)))
+ (when strlst
+ (car strlst))))
+
+(defun nrepl-dispatch-complete-symbol (str)
+ "Return a list of completions for STR.
+Dispatch to the nREPL \"complete\" op if supported,
+otherwise dispatch to internal completion function."
+ (if (nrepl-op-supported-p "complete")
+ (nrepl-completion-complete-op-fn str)
+ (nrepl-completion-complete-core-fn str)))
+
+(defun nrepl-complete-at-point ()
+ "Complete the symbol at point."
+ (let ((sap (symbol-at-point)))
+ (when (and sap (not (in-string-p)))
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (list (car bounds) (cdr bounds)
+ (completion-table-dynamic #'nrepl-dispatch-complete-symbol))))))
+
+(defun nrepl-eldoc-format-thing (thing)
+ "Format the eldoc THING."
+ (propertize thing 'face 'font-lock-function-name-face))
+
+(defun nrepl-highlight-args (arglist pos)
+ "Format the the function ARGLIST for eldoc.
+POS is the index of the currently highlighted argument."
+ (let* ((rest-pos (nrepl--find-rest-args-position arglist))
+ (i 0))
+ (mapconcat
+ (lambda (arg)
+ (let ((argstr (format "%s" arg)))
+ (if (eq arg '&)
+ argstr
+ (prog1
+ (if (or (= (1+ i) pos)
+ (and rest-pos (> (+ 1 i) rest-pos)
+ (> pos rest-pos)))
+ (propertize argstr 'face
+ 'eldoc-highlight-function-argument)
+ argstr)
+ (setq i (1+ i)))))) arglist " ")))
+
+(defun nrepl--find-rest-args-position (arglist)
+ "Find the position of & in the ARGLIST vector."
+ (-elem-index '& (append arglist ())))
+
+(defun nrepl-highlight-arglist (arglist pos)
+ "Format the ARGLIST for eldoc.
+POS is the index of the argument to highlight."
+ (concat "[" (nrepl-highlight-args arglist pos) "]"))
+
+(defun nrepl-eldoc-format-arglist (arglist pos)
+ "Format all the ARGLIST for eldoc.
+POS is the index of current argument."
+ (concat "("
+ (mapconcat (lambda (args) (nrepl-highlight-arglist args pos))
+ (read arglist) " ") ")"))
+
+(defun nrepl-eldoc-info-in-current-sexp ()
+ "Return a list of the current sexp and the current argument index."
+ (save-excursion
+ (let ((argument-index (1- (eldoc-beginning-of-sexp))))
+ ;; If we are at the beginning of function name, this will be -1.
+ (when (< argument-index 0)
+ (setq argument-index 0))
+ ;; Don't do anything if current word is inside a string.
+ (if (= (or (char-after (1- (point))) 0) ?\")
+ nil
+ (list (nrepl-symbol-at-point) argument-index)))))
+
+(defun nrepl-eldoc ()
+ "Backend function for eldoc to show argument list in the echo area."
+ (when (nrepl-current-connection-buffer)
+ (let* ((info (nrepl-eldoc-info-in-current-sexp))
+ (thing (car info))
+ (pos (cadr info))
+ (form (format "(try
+ (:arglists
+ (clojure.core/meta
+ (clojure.core/resolve
+ (clojure.core/read-string \"%s\"))))
+ (catch Throwable t nil))" thing))
+ (result (when thing
+ (nrepl-send-string-sync form
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+ (value (plist-get result :value)))
+ (unless (string= value "nil")
+ (format "%s: %s"
+ (nrepl-eldoc-format-thing thing)
+ (nrepl-eldoc-format-arglist value pos))))))
+
+(defun nrepl-turn-on-eldoc-mode ()
+ "Turn on eldoc mode in the current buffer."
+ (setq-local eldoc-documentation-function 'nrepl-eldoc)
+ (apply 'eldoc-add-command nrepl-extra-eldoc-commands)
+ (turn-on-eldoc-mode))
+
+;;; JavaDoc Browsing
+;;; Assumes local-paths are accessible in the VM.
+(defvar nrepl-javadoc-local-paths nil
+ "List of paths to directories with Javadoc.")
+
+(defun nrepl-javadoc-op (symbol-name)
+ "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME."
+ (nrepl-send-op
+ "javadoc"
+ `("symbol" ,symbol-name "ns" ,nrepl-buffer-ns
+ "local-paths" ,(mapconcat #'identity nrepl-javadoc-local-paths " "))
+ (nrepl-make-response-handler
+ (current-buffer)
+ (lambda (buffer url)
+ (if url
+ (browse-url url)
+ (error "No javadoc url for %s" symbol-name)))
+ nil nil nil)))
+
+(defun nrepl-javadoc-handler (symbol-name)
+ "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME if available."
+ (when symbol-name
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (if (nrepl-op-supported-p "javadoc")
+ (nrepl-javadoc-op symbol-name)
+ (message "No Javadoc middleware available")))))
+
+(defun nrepl-javadoc (query)
+ "Browse Javadoc on the Java class QUERY at point."
+ (interactive "P")
+ (nrepl-read-symbol-name "Javadoc for: " 'nrepl-javadoc-handler query))
+
+;;; Response handlers
+(defmacro nrepl-dbind-response (response keys &rest body)
+ "Destructure an nREPL RESPONSE dict.
+Bind the value of the provided KEYS and execute BODY."
+ `(let ,(loop for key in keys
+ collect `(,key (cdr (assoc ,(format "%s" key) ,response))))
+ ,@body))
+
+(put 'nrepl-dbind-response 'lisp-indent-function 2)
+
+(defun nrepl-make-response-handler
+ (buffer value-handler stdout-handler stderr-handler done-handler
+ &optional eval-error-handler)
+ "Make a response handler for BUFFER.
+Uses the specified VALUE-HANDLER, STDOUT-HANDLER, STDERR-HANDLER,
+DONE-HANDLER, and EVAL-ERROR-HANDLER as appropriate."
+ (lexical-let ((buffer buffer)
+ (value-handler value-handler)
+ (stdout-handler stdout-handler)
+ (stderr-handler stderr-handler)
+ (done-handler done-handler)
+ (eval-error-handler eval-error-handler))
+ (lambda (response)
+ (nrepl-dbind-response response (value ns out err status id ex root-ex
+ session)
+ (cond (value
+ (with-current-buffer buffer
+ (if ns
+ (setq nrepl-buffer-ns ns)))
+ (if value-handler
+ (funcall value-handler buffer value)))
+ (out
+ (if stdout-handler
+ (funcall stdout-handler buffer out)))
+ (err
+ (if stderr-handler
+ (funcall stderr-handler buffer err)))
+ (status
+ (if (member "interrupted" status)
+ (message "Evaluation interrupted."))
+ (if (member "eval-error" status)
+ (funcall (or eval-error-handler nrepl-err-handler)
+ buffer ex root-ex session))
+ (if (member "namespace-not-found" status)
+ (message "Namespace not found."))
+ (if (member "need-input" status)
+ (nrepl-need-input buffer))
+ (if (member "done" status)
+ (progn (remhash id nrepl-requests)
+ (if done-handler
+ (funcall done-handler buffer))))))))))
+
+(defun nrepl-stdin-handler (buffer)
+ "Make a stdin response handler for BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (nrepl-emit-result buffer value t))
+ (lambda (buffer out)
+ (nrepl-emit-output buffer out t))
+ (lambda (buffer err)
+ (nrepl-emit-output buffer err t))
+ nil))
+
+(defun nrepl-handler (buffer)
+ "Make a nrepl evaluation handler for BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (nrepl-emit-result buffer value t))
+ (lambda (buffer out)
+ (nrepl-emit-output buffer out t))
+ (lambda (buffer err)
+ (nrepl-emit-output buffer err t))
+ (lambda (buffer)
+ (nrepl-emit-prompt buffer))))
+
+(defun nrepl-interactive-eval-handler (buffer)
+ "Make an interactive eval handler for BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (message "%s" value))
+ (lambda (buffer value)
+ (nrepl-emit-interactive-output value))
+ (lambda (buffer err)
+ (message "%s" err)
+ (nrepl-highlight-compilation-errors
+ buffer err))
+ '()))
+
+(defun nrepl-load-file-handler (buffer)
+ "Make a load file handler for BUFFER."
+ (let (current-ns (nrepl-current-ns))
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (message "%s" value)
+ (with-current-buffer buffer
+ (setq nrepl-buffer-ns (clojure-find-ns))
+ (run-hooks 'nrepl-file-loaded-hook)))
+ (lambda (buffer value)
+ (nrepl-emit-interactive-output value))
+ (lambda (buffer err)
+ (message "%s" err)
+ (nrepl-highlight-compilation-errors
+ buffer err))
+ '()
+ (lambda (buffer ex root-ex session)
+ (let ((nrepl-popup-on-error nil))
+ (funcall nrepl-err-handler
+ buffer ex root-ex session))))))
+
+(defun nrepl-interactive-eval-print-handler (buffer)
+ "Make a handler for evaluating and printing result in BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (with-current-buffer buffer
+ (insert (format "%s" value))))
+ '()
+ (lambda (buffer err)
+ (message "%s" err))
+ '()))
+
+(defun nrepl-popup-eval-print-handler (buffer)
+ "Make a handler for evaluating and printing result in popup BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ '()
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ '()))
+
+(defun nrepl-popup-eval-out-handler (buffer)
+ "Make a handler for evaluating and printing stdout/stderr in popup BUFFER."
+ (nrepl-make-response-handler buffer
+ '()
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ '()))
+
+(defun nrepl-visit-error-buffer ()
+ "Visit the `nrepl-error-buffer' (usually *nrepl-error*) if it exists."
+ (interactive)
+ (let ((buffer (get-buffer nrepl-error-buffer)))
+ (when buffer
+ (nrepl-popup-buffer-display buffer))))
+
+(defun nrepl-find-property (property &optional backward)
+ "Find the next text region which has the specified PROPERTY.
+If BACKWARD is t, then search backward.
+Returns the position at which PROPERTY was found, or nil if not found."
+ (let ((p (if backward
+ (previous-single-char-property-change (point) property)
+ (next-single-char-property-change (point) property))))
+ (when (and (not (= p (point-min))) (not (= p (point-max))))
+ p)))
+
+(defun nrepl-jump-to-compilation-error (&optional arg reset)
+ "Jump to the line causing the current compilation error.
+
+ARG and RESET are ignored, as there is only ever one compilation error.
+They exist for compatibility with `next-error'."
+ (interactive)
+ (cl-labels ((goto-next-note-boundary
+ ()
+ (let ((p (or (nrepl-find-property 'nrepl-note-p)
+ (nrepl-find-property 'nrepl-note-p t))))
+ (when p
+ (goto-char p)
+ (message (get-char-property p 'nrepl-note))))))
+ ;; if we're already on a compilation error, first jump to the end of
+ ;; it, so that we find the next error.
+ (when (get-char-property (point) 'nrepl-note-p)
+ (goto-next-note-boundary))
+ (goto-next-note-boundary)))
+
+(defun nrepl-default-err-handler (buffer ex root-ex session)
+ "Make an error handler for BUFFER, EX, ROOT-EX and SESSION."
+ ;; TODO: use ex and root-ex as fallback values to display when pst/print-stack-trace-not-found
+ (let ((replp (equal 'nrepl-repl-mode (buffer-local-value 'major-mode buffer))))
+ (if (or (and nrepl-popup-stacktraces-in-repl replp)
+ (and nrepl-popup-stacktraces (not replp)))
+ (lexical-let ((nrepl-popup-on-error nrepl-popup-on-error))
+ (with-current-buffer buffer
+ (nrepl-send-string "(if-let [pst+ (clojure.core/resolve 'clj-stacktrace.repl/pst+)]
+ (pst+ *e) (clojure.stacktrace/print-stack-trace *e))"
+ (nrepl-make-response-handler
+ (nrepl-make-popup-buffer nrepl-error-buffer)
+ nil
+ (lambda (buffer value)
+ (nrepl-emit-into-color-buffer buffer value)
+ (when nrepl-popup-on-error
+ (nrepl-popup-buffer-display buffer nrepl-auto-select-error-buffer)))
+ nil nil) nil session))
+ (with-current-buffer nrepl-error-buffer
+ (compilation-minor-mode +1))))))
+
+(defvar nrepl-compilation-regexp
+ '("\\(?:.*\\(warning, \\)\\|.*?\\(, compiling\\):(\\)\\([^:]*\\):\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?\\(\\(?: - \\(.*\\)\\)\\|)\\)" 3 4 5 (1))
+ "Specifications for matching errors and warnings in Clojure stacktraces.
+See `compilation-error-regexp-alist' for help on their format.")
+
+(add-to-list 'compilation-error-regexp-alist-alist
+ (cons 'nrepl nrepl-compilation-regexp))
+(add-to-list 'compilation-error-regexp-alist 'nrepl)
+
+(defun nrepl-extract-error-info (regexp message)
+ "Extract error information with REGEXP against MESSAGE."
+ (let ((file (nth 1 regexp))
+ (line (nth 2 regexp))
+ (col (nth 3 regexp))
+ (type (nth 4 regexp))
+ (pat (car regexp)))
+ (when (string-match pat message)
+ ;; special processing for type (1.2) style
+ (setq type (if (consp type)
+ (or (and (car type) (match-end (car type)) 1)
+ (and (cdr type) (match-end (cdr type)) 0)
+ 2)))
+ (list
+ (when file
+ (let ((val (match-string-no-properties file message)))
+ (unless (string= val "NO_SOURCE_PATH") val)))
+ (when line (string-to-number (match-string-no-properties line message)))
+ (when col
+ (let ((val (match-string-no-properties col message)))
+ (when val (string-to-number val))))
+ (aref [nrepl-warning-highlight-face
+ nrepl-warning-highlight-face
+ nrepl-error-highlight-face]
+ (or type 2))
+ message))))
+
+(defun nrepl-highlight-compilation-errors (buffer message)
+ "Highlight compilation error line in BUFFER, using MESSAGE."
+ (with-current-buffer buffer
+ (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message)))
+ (when info
+ (let ((file (nth 0 info))
+ (line (nth 1 info))
+ (col (nth 2 info))
+ (face (nth 3 info))
+ (note (nth 4 info)))
+ (save-excursion
+ ;; when we don't have a filename the line number
+ ;; is relative to form start
+ (if file
+ (goto-char (point-min)) ; start of file
+ (beginning-of-defun))
+ (forward-line (1- line))
+ ;; if have column, highlight sexp at that point otherwise whole line.
+ (move-to-column (or col 0))
+ (let ((begin (progn (if col (backward-up-list) (back-to-indentation)) (point)))
+ (end (progn (if col (forward-sexp) (move-end-of-line nil)) (point))))
+ (let ((overlay (make-overlay begin end)))
+ (overlay-put overlay 'nrepl-note-p t)
+ (overlay-put overlay 'face face)
+ (overlay-put overlay 'nrepl-note note)
+ (overlay-put overlay 'help-echo note)))))))))
+
+(defun nrepl-need-input (buffer)
+ "Handle an need-input request from BUFFER."
+ (with-current-buffer buffer
+ (nrepl-send-stdin (concat (read-from-minibuffer "Stdin: ") "\n")
+ (nrepl-stdin-handler buffer))))
+
+
+;;;; Popup buffers
+(define-minor-mode nrepl-popup-buffer-mode
+ "Mode for nrepl popup buffers"
+ nil
+ (" nREPL-tmp")
+ '(("q" . nrepl-popup-buffer-quit-function)))
+
+(make-variable-buffer-local
+ (defvar nrepl-popup-buffer-quit-function 'nrepl-popup-buffer-quit
+ "The function that is used to quit a temporary popup buffer."))
+
+(defun nrepl-popup-buffer-quit-function (&optional kill-buffer-p)
+ "Wrapper to invoke the function `nrepl-popup-buffer-quit-function'.
+KILL-BUFFER-P is passed along."
+ (interactive)
+ (funcall nrepl-popup-buffer-quit-function kill-buffer-p))
+
+(defun nrepl-popup-buffer (name &optional select)
+ "Create new popup buffer called NAME.
+If SELECT is non-nil, select the newly created window"
+ (with-current-buffer (nrepl-make-popup-buffer name)
+ (setq buffer-read-only t)
+ (nrepl-popup-buffer-display (current-buffer) select)))
+
+(defun nrepl-popup-buffer-display (popup-buffer &optional select)
+ "Display POPUP-BUFFER.
+If SELECT is non-nil, select the newly created window"
+ (with-current-buffer popup-buffer
+ (let ((new-window (display-buffer (current-buffer))))
+ (set-window-point new-window (point))
+ (when select
+ (select-window new-window))
+ (current-buffer))))
+
+(defun nrepl-popup-buffer-quit (&optional kill-buffer-p)
+ "Quit the current (temp) window and bury its buffer using `quit-window'.
+If prefix argument KILL-BUFFER-P is non-nil, kill the buffer instead of burying it."
+ (interactive)
+ (quit-window kill-buffer-p (selected-window)))
+
+(defun nrepl-make-popup-buffer (name)
+ "Create a temporary buffer called NAME."
+ (with-current-buffer (get-buffer-create name)
+ (kill-all-local-variables)
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (set-syntax-table clojure-mode-syntax-table)
+ (nrepl-popup-buffer-mode 1)
+ (current-buffer)))
+
+(defun nrepl-emit-into-popup-buffer (buffer value)
+ "Emit into BUFFER the provided VALUE."
+ (with-current-buffer buffer
+ (let ((inhibit-read-only t)
+ (buffer-undo-list t))
+ (insert (format "%s" value))
+ (indent-sexp)
+ (font-lock-fontify-buffer))))
+
+(defun nrepl-emit-into-color-buffer (buffer value)
+ "Emit into color BUFFER the provided VALUE."
+ (with-current-buffer buffer
+ (let ((inhibit-read-only t)
+ (buffer-undo-list t))
+ (goto-char (point-max))
+ (insert (format "%s" value))
+ (ansi-color-apply-on-region (point-min) (point-max)))
+ (goto-char (point-min))))
+
+
+;;;; Macroexpansion
+(defun nrepl-macroexpand-undo (&optional arg)
+ "Undo the last macroexpansion, using `undo-only'.
+ARG is passed along to `undo-only'."
+ (interactive)
+ (let ((inhibit-read-only t))
+ (undo-only arg)))
+
+(defvar nrepl-last-macroexpand-expression nil
+ "Specify the last macroexpansion preformed.
+This variable specifies both what was expanded and the expander.")
+
+(defun nrepl-macroexpand-form (expander expr)
+ "Macroexpand, using EXPANDER, the given EXPR."
+ (format
+ "(clojure.pprint/write (%s '%s) :suppress-namespaces false :dispatch clojure.pprint/code-dispatch)"
+ expander expr))
+
+(defun nrepl-macroexpand-expr (expander expr &optional buffer)
+ "Macroexpand, use EXPANDER, the given EXPR from BUFFER."
+ (let* ((form (nrepl-macroexpand-form expander expr))
+ (expansion (plist-get (nrepl-send-string-sync form nrepl-buffer-ns) :stdout)))
+ (setq nrepl-last-macroexpand-expression form)
+ (nrepl-initialize-macroexpansion-buffer expansion nrepl-buffer-ns)))
+
+(defun nrepl-macroexpand-expr-inplace (expander)
+ "Substitute the current form at point with its macroexpansion using EXPANDER."
+ (interactive)
+ (let ((form-with-bounds (nrepl-sexp-at-point-with-bounds)))
+ (if form-with-bounds
+ (destructuring-bind (expr bounds) form-with-bounds
+ (let* ((form (nrepl-macroexpand-form expander expr))
+ (expansion (plist-get (nrepl-send-string-sync form nrepl-buffer-ns) :stdout)))
+ (nrepl-redraw-macroexpansion-buffer
+ expansion (current-buffer) (car bounds) (cdr bounds) (point)))))))
+
+(defun nrepl-macroexpand-again ()
+ "Repeat the last macroexpansion."
+ (interactive)
+ (let ((expansion
+ (plist-get (nrepl-send-string-sync nrepl-last-macroexpand-expression nrepl-buffer-ns) :stdout)))
+ (nrepl-initialize-macroexpansion-buffer expansion nrepl-buffer-ns)))
+
+(defun nrepl-macroexpand-1 (&optional prefix)
+ "Invoke 'macroexpand-1' on the expression at point.
+If invoked with a PREFIX argument, use 'macroexpand' instead of
+'macroexpand-1'."
+ (interactive "P")
+ (let ((expander (if prefix 'macroexpand 'macroexpand-1)))
+ (nrepl-macroexpand-expr expander (nrepl-sexp-at-point))))
+
+(defun nrepl-macroexpand-1-inplace (&optional prefix)
+ "Perform inplace 'macroexpand-1' on the expression at point.
+If invoked with a PREFIX argument, use 'macroexpand' instead of
+'macroexpand-1'."
+ (interactive "P")
+ (let ((expander (if prefix 'macroexpand 'macroexpand-1)))
+ (nrepl-macroexpand-expr-inplace expander)))
+
+(defun nrepl-macroexpand-all ()
+ "Invoke 'clojure.walk/macroexpand-all' on the expression at point."
+ (interactive)
+ (nrepl-macroexpand-expr
+ 'clojure.walk/macroexpand-all (nrepl-sexp-at-point)))
+
+(defun nrepl-macroexpand-all-inplace ()
+ "Perform inplace 'clojure.walk/macroexpand-all' on the expression at point."
+ (interactive)
+ (nrepl-macroexpand-expr-inplace 'clojure.walk/macroexpand-all))
+
+(defun nrepl-initialize-macroexpansion-buffer (expansion ns)
+ "Create a new Macroexpansion buffer with EXPANSION and namespace NS."
+ (pop-to-buffer (nrepl-create-macroexpansion-buffer))
+ (setq nrepl-buffer-ns ns)
+ (setq buffer-undo-list nil)
+ (let ((inhibit-read-only t)
+ (buffer-undo-list t))
+ (erase-buffer)
+ (insert (format "%s" expansion))
+ (goto-char (point-min))
+ (font-lock-fontify-buffer)))
+
+(defun nrepl-redraw-macroexpansion-buffer (expansion buffer start end current-point)
+ "Redraw the macroexpansion with new EXPANSION.
+Text in BUFFER from START to END is replaced with new expansion,
+and point is placed at CURRENT-POINT."
+ (with-current-buffer buffer
+ (let ((buffer-read-only nil))
+ (goto-char start)
+ (delete-region start end)
+ (insert (format "%s" expansion))
+ (goto-char start)
+ (indent-sexp)
+ (goto-char current-point))))
+
+
+(defun nrepl-popup-eval-print (form)
+ "Evaluate the given FORM and print value in current buffer."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form
+ (nrepl-popup-eval-print-handler buffer)
+ (nrepl-current-ns))))
+
+(defun nrepl-interactive-eval-print (form)
+ "Evaluate the given FORM and print value in current buffer."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form
+ (nrepl-interactive-eval-print-handler buffer)
+ (nrepl-current-ns))))
+
+(defun nrepl-interactive-eval (form)
+ "Evaluate the given FORM and print value in minibuffer."
+ (remove-overlays (point-min) (point-max) 'nrepl-note-p t)
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form
+ (nrepl-interactive-eval-handler buffer)
+ (nrepl-current-ns))))
+
+(defun nrepl-send-op (op attributes handler)
+ "Send the specified OP with ATTRIBUTES and response HANDLER."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-request (append
+ (list "op" op
+ "session" (nrepl-current-session)
+ "ns" nrepl-buffer-ns)
+ attributes)
+ handler)))
+
+(defun nrepl-send-load-file (file-contents file-path file-name)
+ "Perform the nREPL \"load-file\" op.
+FILE-CONTENTS, FILE-PATH and FILE-NAME are details of the file to be
+loaded."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-request (list "op" "load-file"
+ "session" (nrepl-current-session)
+ "file" file-contents
+ "file-path" file-path
+ "file-name" file-name)
+ (nrepl-load-file-handler buffer))))
+
+(defun nrepl-eval-last-expression (&optional prefix)
+ "Evaluate the expression preceding point.
+If invoked with a PREFIX argument, print the result in the current buffer."
+ (interactive "P")
+ (if prefix
+ (nrepl-interactive-eval-print (nrepl-last-expression))
+ (nrepl-interactive-eval (nrepl-last-expression))))
+
+(defun nrepl-eval-print-last-expression ()
+ "Evaluate the expression preceding point.
+Print its value into the current buffer"
+ (interactive)
+ (nrepl-interactive-eval-print (nrepl-last-expression)))
+
+(defun nrepl-pprint-eval-last-expression ()
+ "Evaluate the expression preceding point and pprint its value in a popup buffer."
+ (interactive)
+ (let ((form (nrepl-last-expression))
+ (result-buffer (nrepl-popup-buffer nrepl-result-buffer nil)))
+ (nrepl-send-string (format "(clojure.pprint/pprint %s)" form)
+ (nrepl-popup-eval-out-handler result-buffer)
+ (nrepl-current-ns)
+ (nrepl-current-tooling-session))))
+
+
+(defun nrepl-create-macroexpansion-buffer ()
+ "Create a new macroexpansion buffer."
+ (with-current-buffer (nrepl-popup-buffer nrepl-macroexpansion-buffer t)
+ (clojure-mode)
+ (clojure-disable-nrepl)
+ (nrepl-macroexpansion-minor-mode 1)
+ (current-buffer)))
+
+
+(defun clojure-enable-nrepl ()
+ "Turn on nrepl interaction mode (see command `nrepl-interaction-mode').
+Useful in hooks."
+ (nrepl-interaction-mode 1)
+ (setq next-error-function 'nrepl-jump-to-compilation-error))
+
+(defun clojure-disable-nrepl ()
+ "Turn off nrepl interaction mode (see command `nrepl-interaction-mode').
+Useful in hooks."
+ (nrepl-interaction-mode -1))
+
+;;; communication
+(defcustom nrepl-lein-command
+ "lein"
+ "The command used to execute leiningen 2.x."
+ :type 'string
+ :group 'nrepl-repl-mode)
+
+(defcustom nrepl-server-command
+ (if (or (locate-file nrepl-lein-command exec-path)
+ (locate-file (format "%s.bat" nrepl-lein-command) exec-path))
+ (format "%s repl :headless" nrepl-lein-command)
+ (format "echo \"%s repl :headless\" | eval $SHELL -l" nrepl-lein-command))
+ "The command used to start the nREPL via command `nrepl-jack-in'.
+For a remote nREPL server lein must be in your PATH. The remote
+proc is launched via sh rather than bash, so it might be necessary
+to specific the full path to it. Localhost is assumed."
+ :type 'string
+ :group 'nrepl-repl-mode)
+
+
+(defun nrepl-default-handler (response)
+ "Default handler which is invoked when no handler is found.
+Handles message contained in RESPONSE."
+ (nrepl-dbind-response response (out value)
+ (cond
+ (out
+ (nrepl-emit-interactive-output out)))))
+
+(defun nrepl-dispatch (response)
+ "Dispatch the RESPONSE to associated callback."
+ (nrepl-log-event response)
+ (nrepl-dbind-response response (id)
+ (let ((callback (gethash id nrepl-requests)))
+ (if callback
+ (funcall callback response)
+ (nrepl-default-handler response)))))
+
+(defun nrepl-net-decode ()
+ "Decode the data in the current buffer.
+Remove the processed data from the buffer if the decode successful."
+ (let* ((start (point-min))
+ (end (point-max))
+ (data (buffer-substring start end)))
+ (prog1
+ (nrepl-decode data)
+ (delete-region start end))))
+
+(defun nrepl-net-process-input (process)
+ "Handle all complete messages from PROCESS.
+Assume that any error during decoding indicates an incomplete message."
+ (with-current-buffer (process-buffer process)
+ (let ((nrepl-connection-dispatch (current-buffer)))
+ (ignore-errors
+ (while (> (buffer-size) 1)
+ (let ((responses (nrepl-net-decode)))
+ (dolist (response responses)
+ (nrepl-dispatch response))))))))
+
+(defun nrepl-net-filter (process string)
+ "Decode the message(s) from PROCESS contained in STRING and dispatch."
+ (with-current-buffer (process-buffer process)
+ (goto-char (point-max))
+ (insert string))
+ (nrepl-net-process-input process))
+
+(defun nrepl-sentinel (process message)
+ "Handle sentinel events from PROCESS.
+Display MESSAGE and if the process is closed kill the
+process buffer and run the hook `nrepl-disconnected-hook'."
+ (message "nrepl connection closed: %s" message)
+ (if (equal (process-status process) 'closed)
+ (progn
+ (with-current-buffer (process-buffer process)
+ (when (get-buffer nrepl-repl-buffer)
+ (kill-buffer nrepl-repl-buffer))
+ (kill-buffer (current-buffer)))
+ (run-hooks 'nrepl-disconnected-hook))))
+
+(defun nrepl-write-message (process message)
+ "Send the PROCESS the MESSAGE."
+ (process-send-string process message))
+
+;;; Log nrepl events
+
+(defcustom nrepl-log-events nil
+ "Log protocol events to the *nrepl-events* buffer."
+ :type 'boolean
+ :group 'nrepl)
+
+(defconst nrepl-event-buffer-name "*nrepl-events*"
+ "Event buffer for nREPL message logging.")
+
+(defconst nrepl-event-buffer-max-size 50000
+ "Maximum size for the nREPL event buffer.
+Defaults to 50000 characters, which should be an insignificant
+memory burdon, while providing reasonable history.")
+
+(defconst nrepl-event-buffer-reduce-denominator 4
+ "Divisor by which to reduce event buffer size.
+When the maximum size for the nREPL event buffer is exceed, the
+size of the buffer is reduced by one over this value. Defaults
+to 4, so that 1/4 of the buffer is removed, which should ensure
+the buffer's maximum is reasonably utilised, while limiting the
+number of buffer shrinking operations.")
+
+(defun nrepl-log-event (msg)
+ "Log the given MSG to the buffer given by `nrepl-event-buffer-name'.
+The default buffer name is *nrepl-events*."
+ (when nrepl-log-events
+ (with-current-buffer (nrepl-events-buffer)
+ (when (> (buffer-size) nrepl-event-buffer-max-size)
+ (goto-char (/ (buffer-size) nrepl-event-buffer-reduce-denominator))
+ (re-search-forward "^(" nil t)
+ (delete-region (point-min) (- (point) 1)))
+ (goto-char (point-max))
+ (pp msg (current-buffer)))))
+
+(defun nrepl-events-buffer ()
+ "Return or create the buffer given by `nrepl-event-buffer-name'.
+The default buffer name is *nrepl-events*."
+ (or (get-buffer nrepl-event-buffer-name)
+ (let ((buffer (get-buffer-create nrepl-event-buffer-name)))
+ (with-current-buffer buffer
+ (buffer-disable-undo)
+ (setq-local comment-start ";")
+ (setq-local comment-end ""))
+ buffer)))
+
+(defun nrepl-log-events (&optional disable)
+ "Turn on event logging to *nrepl-events*.
+With a prefix argument DISABLE, turn it off."
+ (interactive "P")
+ (setq nrepl-log-events (not disable)))
+
+
+;;; Connections
+
+;;; A connection is the communication between the nrepl.el client and an nrepl
+;;; server.
+
+(defvar nrepl-connection-dispatch nil
+ "Bound to the connection a message was received on.
+This is bound for the duration of the handling of that message")
+
+(defvar nrepl-connection-list nil
+ "A list of connections.")
+
+(defun nrepl-make-connection-buffer ()
+ "Create an nREPL connection buffer."
+ (let ((buffer (generate-new-buffer (nrepl-connection-buffer-name))))
+ (with-current-buffer buffer
+ (buffer-disable-undo)
+ (setq-local kill-buffer-query-functions nil))
+ buffer))
+
+(defun nrepl-current-connection-buffer ()
+ "The connection to use for nREPL interaction."
+ (or nrepl-connection-dispatch
+ nrepl-connection-buffer
+ (car (nrepl-connection-buffers))))
+
+(defun nrepl-connection-buffers ()
+ "Clean up dead buffers from the `nrepl-connection-list'.
+Return the connection list."
+ (nrepl--connection-list-purge)
+ nrepl-connection-list)
+
+(defun nrepl--connection-list-purge ()
+ "Clean up dead buffers from the `nrepl-connection-list'."
+ (setq nrepl-connection-list
+ (-remove (lambda (buffer)
+ (not (buffer-live-p (get-buffer buffer))))
+ nrepl-connection-list)))
+
+(defun nrepl-make-repl-connection-default (connection-buffer)
+ "Make the nREPL CONNECTION-BUFFER the default connection.
+Moves CONNECITON-BUFFER to the front of `nrepl-connection-list'."
+ (interactive (list nrepl-connection-buffer))
+ (if connection-buffer
+ ;; maintain the connection list in most recently used order
+ (lexical-let ((buf-name (buffer-name (get-buffer connection-buffer))))
+ (setq nrepl-connection-list
+ (cons buf-name (delq buf-name nrepl-connection-list)))
+ (nrepl--connections-refresh))
+ (message "Not in an nREPL REPL buffer.")))
+
+(defun nrepl--close-connection-buffer (connection-buffer)
+ "Closes CONNECTION-BUFFER, removing it from `nrepl-connection-list'.
+Also closes associated REPL and server buffers."
+ (let ((nrepl-connection-dispatch connection-buffer))
+ (lexical-let ((buffer (get-buffer connection-buffer)))
+ (setq nrepl-connection-list
+ (delq (buffer-name buffer) nrepl-connection-list))
+ (when (buffer-live-p buffer)
+ (dolist (buf-name `(,(buffer-local-value 'nrepl-repl-buffer buffer)
+ ,(buffer-local-value 'nrepl-server-buffer buffer)
+ ,buffer))
+ (when buf-name
+ (nrepl--close-buffer buf-name)))))))
+
+(defun nrepl-current-repl-buffer ()
+ "The current nrepl buffer."
+ (when (nrepl-current-connection-buffer)
+ (buffer-local-value 'nrepl-repl-buffer
+ (get-buffer (nrepl-current-connection-buffer)))))
+
+;;; Connection browser
+(defvar nrepl-connections-buffer-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "d" 'nrepl-connections-make-default)
+ (define-key map "g" 'nrepl-connection-browser)
+ (define-key map (kbd "C-k") 'nrepl-connections-close-connection)
+ (define-key map (kbd "RET") 'nrepl-connections-goto-connection)
+ map))
+
+(define-derived-mode nrepl-connections-buffer-mode nrepl-popup-buffer-mode
+ "nREPL-Connections"
+ "nREPL Connections Buffer Mode.
+\\{nrepl-connections-buffer-mode-map}
+\\{nrepl-popup-buffer-mode-map}"
+ (setq-local truncate-lines t))
+
+(defvar nrepl--connection-ewoc)
+(defconst nrepl--connection-browser-buffer-name "*nrepl-connections*")
+
+(defun nrepl-connection-browser ()
+ "Open a browser buffer for nREPL connections."
+ (interactive)
+ (lexical-let ((buffer (get-buffer nrepl--connection-browser-buffer-name)))
+ (if buffer
+ (progn
+ (nrepl--connections-refresh-buffer buffer)
+ (unless (get-buffer-window buffer)
+ (select-window (display-buffer buffer))))
+ (nrepl--setup-connection-browser))))
+
+(defun nrepl--connections-refresh ()
+ "Refresh the connections buffer, if the buffer exists.
+The connections buffer is determined by
+`nrepl--connection-browser-buffer-name'"
+ (lexical-let ((buffer (get-buffer nrepl--connection-browser-buffer-name)))
+ (when buffer
+ (nrepl--connections-refresh-buffer buffer))))
+
+(defun nrepl--connections-refresh-buffer (buffer)
+ "Refresh the connections BUFFER."
+ (nrepl--update-connections-display
+ (buffer-local-value 'nrepl--connection-ewoc buffer)
+ nrepl-connection-list))
+
+(defun nrepl--setup-connection-browser ()
+ "Create a browser buffer for nREPL connections."
+ (with-current-buffer (get-buffer-create nrepl--connection-browser-buffer-name)
+ (lexical-let ((ewoc (ewoc-create
+ 'nrepl--connection-pp
+ " Host Port Project\n")))
+ (setq-local nrepl--connection-ewoc ewoc)
+ (nrepl--update-connections-display ewoc nrepl-connection-list)
+ (setq buffer-read-only t)
+ (nrepl-connections-buffer-mode)
+ (display-buffer (current-buffer)))))
+
+(defun nrepl--connection-pp (connection)
+ "Print an nREPL CONNECTION to the current buffer."
+ (lexical-let* ((buffer-read-only nil)
+ (buffer (get-buffer connection))
+ (endpoint (buffer-local-value 'nrepl-endpoint buffer)))
+ (insert
+ (format "%s %-16s %5s %s"
+ (if (equal connection (car nrepl-connection-list)) "*" " ")
+ (car endpoint)
+ (prin1-to-string (cadr endpoint))
+ (or (nrepl--project-name
+ (buffer-local-value 'nrepl-project-dir buffer))
+ "")))))
+
+(defun nrepl--project-name (path)
+ "Extracts a project name from PATH, possibly nil.
+The project name is the final component of PATH if not nil."
+ (when path
+ (file-name-nondirectory (directory-file-name path))))
+
+(defun nrepl--update-connections-display (ewoc connections)
+ "Update the connections EWOC to show CONNECTIONS."
+ (ewoc-filter ewoc (lambda (n) (member n connections)))
+ (let ((existing))
+ (ewoc-map (lambda (n) (setq existing (cons n existing))) ewoc)
+ (lexical-let ((added (-difference connections existing)))
+ (mapc (apply-partially 'ewoc-enter-last ewoc) added)
+ (save-excursion (ewoc-refresh ewoc)))))
+
+(defun nrepl--ewoc-apply-at-point (f)
+ "Apply function F to the ewoc node at point.
+F is a function of two arguments, the ewoc and the data at point."
+ (lexical-let* ((ewoc nrepl--connection-ewoc)
+ (node (and ewoc (ewoc-locate ewoc))))
+ (when node
+ (funcall f ewoc (ewoc-data node)))))
+
+(defun nrepl-connections-make-default ()
+ "Make default the connection at point in the connection browser."
+ (interactive)
+ (save-excursion
+ (nrepl--ewoc-apply-at-point #'nrepl--connections-make-default)))
+
+(defun nrepl--connections-make-default (ewoc data)
+ "Make the connection in EWOC specified by DATA default.
+Refreshes EWOC."
+ (interactive)
+ (nrepl-make-repl-connection-default data)
+ (ewoc-refresh ewoc))
+
+(defun nrepl-connections-close-connection ()
+ "Close connection at point in the connection browser."
+ (interactive)
+ (nrepl--ewoc-apply-at-point #'nrepl--connections-close-connection))
+
+(defun nrepl--connections-close-connection (ewoc data)
+ "Close the connection in EWOC specified by DATA."
+ (nrepl-close (get-buffer data))
+ (nrepl--update-connections-display ewoc nrepl-connection-list))
+
+(defun nrepl-connections-goto-connection ()
+ "Goto connection at point in the connection browser."
+ (interactive)
+ (nrepl--ewoc-apply-at-point #'nrepl--connections-goto-connection))
+
+(defun nrepl--connections-goto-connection (ewoc data)
+ "Goto the REPL for the connection in EWOC specified by DATA."
+ (let ((buffer (buffer-local-value 'nrepl-repl-buffer (get-buffer data))))
+ (when buffer
+ (select-window (display-buffer buffer)))))
+
+(defun nrepl--clojure-version ()
+ "Retrieve the underlying connection's Clojure version."
+ (let ((version-string (plist-get (nrepl-send-string-sync "(clojure-version)") :value)))
+ (substring version-string 1 (1- (length version-string)))))
+
+(defun nrepl--backend-version ()
+ "Retrieve the underlying connection's nREPL version."
+ (let ((version-string (plist-get (nrepl-send-string-sync "(:version-string clojure.tools.nrepl/version)") :value)))
+ (substring version-string 1 (1- (length version-string)))))
+
+(defun nrepl--connection-info (nrepl-connection-buffer)
+ "Return info about NREPL-CONNECTION-BUFFER.
+
+Info contains project name, current REPL namespace, host:port endpoint and Clojure version."
+ (with-current-buffer (get-buffer nrepl-connection-buffer)
+ (format "Active nrepl connection: %s:%s, %s:%s (Clojure %s, nREPL %s)"
+ (or (nrepl--project-name nrepl-project-dir) "<no project>")
+ nrepl-buffer-ns
+ (car nrepl-endpoint)
+ (cadr nrepl-endpoint)
+ (nrepl--clojure-version)
+ (nrepl--backend-version))))
+
+(defun nrepl-display-current-connection-info ()
+ "Display information about the current connection."
+ (interactive)
+ (message (nrepl--connection-info (nrepl-current-connection-buffer))))
+
+(defun nrepl-rotate-connection ()
+ "Rotate and display the current nrepl connection."
+ (interactive)
+ (setq nrepl-connection-list
+ (append (cdr nrepl-connection-list)
+ (list (car nrepl-connection-list))))
+ (message (nrepl--connection-info (car nrepl-connection-list))))
+
+;;; server messages
+
+(defun nrepl-current-session ()
+ "Return the current session."
+ (with-current-buffer (nrepl-current-connection-buffer)
+ nrepl-session))
+
+(defun nrepl-current-tooling-session ()
+ "Return the current tooling session."
+ (with-current-buffer (nrepl-current-connection-buffer)
+ nrepl-tooling-session))
+
+(defun nrepl-next-request-id ()
+ "Return the next request id."
+ (with-current-buffer (nrepl-current-connection-buffer)
+ (number-to-string (incf nrepl-request-counter))))
+
+(defun nrepl-send-request (request callback)
+ "Send REQUEST and register response handler CALLBACK."
+ (let* ((request-id (nrepl-next-request-id))
+ (request (append (list "id" request-id) request))
+ (message (nrepl-bencode request)))
+ (nrepl-log-event request)
+ (puthash request-id callback nrepl-requests)
+ (nrepl-write-message (nrepl-current-connection-buffer) message)))
+
+(defun nrepl-create-client-session (callback)
+ "Sent a request to create a new client session.
+Response will be handled by CALLBACK."
+ (nrepl-send-request '("op" "clone")
+ callback))
+
+(defun nrepl-send-stdin (input callback)
+ "Send a stdin message with INPUT.
+Register CALLBACK as the response handler."
+ (nrepl-send-request (list "op" "stdin"
+ "stdin" input
+ "session" (nrepl-current-session))
+ callback))
+
+(defun nrepl-send-interrupt (pending-request-id callback)
+ "Send an interrupt message for PENDING-REQUEST-ID.
+Register CALLBACK as the response handler."
+ (nrepl-send-request (list "op" "interrupt"
+ "session" (nrepl-current-session)
+ "interrupt-id" pending-request-id)
+ callback))
+
+(defun nrepl-eval-request (input &optional ns session)
+ "Send a request to eval INPUT.
+If NS is non-nil, include it in the request.
+Use SESSION if it is non-nil, otherwise use the current session."
+ (append (if ns (list "ns" ns))
+ (list
+ "op" "eval"
+ "session" (or session (nrepl-current-session))
+ "code" input)))
+
+(defun nrepl-send-string (input callback &optional ns session)
+ "Send the request INPUT and register the CALLBACK as the response handler.
+See command `nrepl-eval-request' for details on how NS and SESSION are processed."
+ (let ((ns (if (string-match "[[:space:]]*\(ns\\([[:space:]]*$\\|[[:space:]]+\\)" input)
+ "user"
+ ns)))
+ (nrepl-send-request (nrepl-eval-request input ns session) callback)))
+
+(defun nrepl-sync-request-handler (buffer)
+ "Make a synchronous request handler for BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (setq nrepl-sync-response
+ (plist-put nrepl-sync-response :value value)))
+ (lambda (buffer out)
+ (let ((so-far (plist-get nrepl-sync-response :stdout)))
+ (setq nrepl-sync-response
+ (plist-put nrepl-sync-response
+ :stdout (concat so-far out)))))
+ (lambda (buffer err)
+ (let ((so-far (plist-get nrepl-sync-response :stderr)))
+ (setq nrepl-sync-response
+ (plist-put nrepl-sync-response
+ :stderr (concat so-far err)))))
+ (lambda (buffer)
+ (setq nrepl-sync-response
+ (plist-put nrepl-sync-response :done t)))))
+
+(defun nrepl-send-request-sync (request)
+ "Send REQUEST to the backend synchronously (discouraged).
+The result is a plist with keys :value, :stderr and :stdout."
+ (with-current-buffer (nrepl-current-connection-buffer)
+ (setq nrepl-sync-response nil)
+ (nrepl-send-request request (nrepl-sync-request-handler (current-buffer)))
+ (while (or (null nrepl-sync-response)
+ (null (plist-get nrepl-sync-response :done)))
+ (accept-process-output nil 0.005))
+ nrepl-sync-response))
+
+(defun nrepl-send-string-sync (input &optional ns session)
+ "Send the INPUT to the backend synchronously.
+See command `nrepl-eval-request' for details about how NS and SESSION
+are processed."
+ (nrepl-send-request-sync (nrepl-eval-request input ns session)))
+
+(defalias 'nrepl-eval 'nrepl-send-string-sync)
+(defalias 'nrepl-eval-async 'nrepl-send-string)
+
+(defun nrepl-send-input (&optional newline)
+ "Go to the end of the input and send the current input.
+If NEWLINE is true then add a newline at the end of the input."
+ (unless (nrepl-in-input-area-p)
+ (error "No input at point"))
+ (goto-char (point-max))
+ (let ((end (point))) ; end of input, without the newline
+ (nrepl-add-to-input-history (buffer-substring nrepl-input-start-mark end))
+ (when newline
+ (insert "\n")
+ (nrepl-show-maximum-output))
+ (let ((inhibit-modification-hooks t))
+ (add-text-properties nrepl-input-start-mark
+ (point)
+ `(nrepl-old-input
+ ,(incf nrepl-old-input-counter))))
+ (let ((overlay (make-overlay nrepl-input-start-mark end)))
+ ;; These properties are on an overlay so that they won't be taken
+ ;; by kill/yank.
+ (overlay-put overlay 'read-only t)
+ (overlay-put overlay 'face 'nrepl-input-face)))
+ (let* ((input (nrepl-current-input))
+ (form (if (and (not (string-match "\\`[ \t\r\n]*\\'" input)) nrepl-use-pretty-printing)
+ (format "(clojure.pprint/pprint %s)" input) input)))
+ (goto-char (point-max))
+ (nrepl-mark-input-start)
+ (nrepl-mark-output-start)
+ (nrepl-send-string form (nrepl-handler (current-buffer)) nrepl-buffer-ns)))
+
+(defun nrepl-find-ns ()
+ "Return the ns specified in the buffer, or \"user\" if no ns declaration is found."
+ (or (save-restriction
+ (widen)
+ (clojure-find-ns))
+ "user"))
+
+(defun nrepl-current-ns ()
+ "Return the ns in the current context.
+If `nrepl-buffer-ns' has a value then return that, otherwise
+search for and read a `ns' form."
+ (let ((ns nrepl-buffer-ns))
+ (or (and (string= ns "user")
+ (nrepl-find-ns))
+ ns)))
+
+(make-variable-buffer-local
+ (defvar nrepl-last-clojure-buffer nil
+ "A buffer-local variable holding the last Clojure source buffer.
+`nrepl-switch-to-last-clojure-buffer' uses this variable to jump
+back to last Clojure source buffer."))
+
+(defvar nrepl-current-clojure-buffer nil
+ "This variable holds current buffer temporarily when connecting to a REPL.
+It is set to current buffer when `nrepl' or `nrepl-jack-in' is called.
+After the REPL buffer is created, the value of this variable is used
+to call `nrepl-remember-clojure-buffer'.")
+
+(defun nrepl-remember-clojure-buffer (buffer)
+ "Try to remember the BUFFER from which the user jumps.
+The BUFFER needs to be a Clojure buffer and current major mode needs
+to be `nrepl-repl-mode'. The user can use `nrepl-switch-to-last-clojure-buffer'
+to jump back to the last Clojure source buffer."
+ (when (and buffer
+ (eq 'clojure-mode (with-current-buffer buffer major-mode))
+ (eq 'nrepl-repl-mode major-mode))
+ (setq nrepl-last-clojure-buffer buffer)))
+
+(defun nrepl-switch-to-repl-buffer (arg)
+ "Select the REPL buffer, when possible in an existing window.
+
+Hint: You can use `display-buffer-reuse-frames' and
+`special-display-buffer-names' to customize the frame in which
+the buffer should appear.
+
+With a prefix ARG sets the name of the REPL buffer to the one
+of the current source file."
+ (interactive "P")
+ (if (not (get-buffer (nrepl-current-connection-buffer)))
+ (message "No active nREPL connection.")
+ (progn
+ (let ((buffer (current-buffer)))
+ (when arg
+ (nrepl-set-ns (nrepl-current-ns)))
+ (pop-to-buffer (nrepl-find-or-create-repl-buffer))
+ (nrepl-remember-clojure-buffer buffer)
+ (goto-char (point-max))))))
+
+(defun nrepl-switch-to-relevant-repl-buffer (arg)
+ "Select the REPL buffer, when possible in an existing window.
+The buffer chosen is based on the file open in the current buffer.
+
+Hint: You can use `display-buffer-reuse-frames' and
+`special-display-buffer-names' to customize the frame in which
+the buffer should appear.
+
+With a prefix ARG sets the name of the REPL buffer to the one
+of the current source file.
+
+With a second prefix ARG the chosen REPL buffer is based on a
+supplied project directory."
+ (interactive "P")
+ (if (not (get-buffer (nrepl-current-connection-buffer)))
+ (message "No active nREPL connection.")
+ (progn
+ (let ((project-directory
+ (or (when arg
+ (ido-read-directory-name "Project: "))
+ (nrepl-project-directory-for (nrepl-current-dir)))))
+ (if project-directory
+ (let ((buf (car (-filter
+ (lambda (conn)
+ (let ((conn-proj-dir (with-current-buffer (get-buffer conn)
+ nrepl-project-dir)))
+ (when conn-proj-dir
+ (equal (file-truename project-directory)
+ (file-truename conn-proj-dir)))))
+ nrepl-connection-list))))
+ (if buf
+ (setq nrepl-connection-list
+ (cons buf (delq buf nrepl-connection-list)))
+ (message "No relevant nREPL connection found. Switching to default connection.")))
+ (message "No project directory found. Switching to default nREPL connection.")))
+ (nrepl-switch-to-repl-buffer '()))))
+
+(defun nrepl-switch-to-last-clojure-buffer ()
+ "Switch to the last Clojure buffer.
+The default keybinding for this command is
+the same as `nrepl-switch-to-repl-buffer',
+so that it is very convenient to jump between a
+Clojure buffer and the REPL buffer."
+ (interactive)
+ (if (and (eq 'nrepl-repl-mode major-mode)
+ (buffer-live-p nrepl-last-clojure-buffer))
+ (pop-to-buffer nrepl-last-clojure-buffer)
+ (message "Don't know the original Clojure buffer")))
+
+(defun nrepl-set-ns (ns)
+ "Switch the namespace of the REPL buffer to NS."
+ (interactive (list (nrepl-current-ns)))
+ (if ns
+ (with-current-buffer (nrepl-current-repl-buffer)
+ (nrepl-send-string
+ (format "(in-ns '%s)" ns) (nrepl-handler (current-buffer))))
+ (message "Sorry, I don't know what the current namespace is.")))
+
+(defun nrepl-symbol-at-point ()
+ "Return the name of the symbol at point, otherwise nil."
+ (let ((str (thing-at-point 'symbol)))
+ (and str
+ (not (equal str (concat (nrepl-find-ns) "> ")))
+ (not (equal str ""))
+ (substring-no-properties str))))
+
+;; this is horrible, but with async callbacks we can't rely on dynamic scope
+(defvar nrepl-ido-ns nil)
+
+(defun nrepl-ido-form (ns)
+ "Construct a Clojure form for ido read using NS."
+ `(concat (if (find-ns (symbol ,ns))
+ (map name (concat (keys (ns-interns (symbol ,ns)))
+ (keys (ns-refers (symbol ,ns))))))
+ (if (not= "" ,ns) [".."])
+ (->> (all-ns)
+ (map (fn [n]
+ (re-find (re-pattern (str "^" (if (not= ,ns "")
+ (str ,ns "\\."))
+ "[^\\.]+"))
+ (str n))))
+ (filter identity)
+ (map (fn [n] (str n "/")))
+ (into (hash-set)))))
+
+(defun nrepl-ido-up-ns (ns)
+ "Perform up using NS."
+ (mapconcat 'identity (butlast (split-string ns "\\.")) "."))
+
+(defun nrepl-ido-select (selected targets callback)
+ "Peform ido select using SELECTED, TARGETS and CALLBACK."
+ ;; TODO: immediate RET gives "" as selected for some reason
+ ;; this is an OK workaround though
+ (cond ((equal "" selected)
+ (nrepl-ido-select (car targets) targets callback))
+ ((equal "/" (substring selected -1)) ; selected a namespace
+ (nrepl-ido-read-var (substring selected 0 -1) callback))
+ ((equal ".." selected)
+ (nrepl-ido-read-var (nrepl-ido-up-ns nrepl-ido-ns) callback))
+ ;; non ido variable selection techniques don't return qualified symbols, so this shouldn't either
+ (t (funcall callback selected))))
+
+(defun nrepl-ido-read-var-handler (ido-callback buffer)
+ "Create an ido read var handler with IDO-CALLBACK for BUFFER."
+ (lexical-let ((ido-callback ido-callback))
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ ;; make sure to eval the callback in the buffer that the symbol was requested from so we get the right namespace
+ (with-current-buffer buffer
+ (let* ((targets (car (read-from-string value)))
+ (selected (ido-completing-read "Var: " targets nil t)))
+ (nrepl-ido-select selected targets ido-callback))))
+ nil nil nil)))
+
+(defun nrepl-ido-read-var (ns ido-callback)
+ "Perform ido read var in NS using IDO-CALLBACK."
+ ;; Have to be stateful =(
+ (setq nrepl-ido-ns ns)
+ (nrepl-send-string (prin1-to-string (nrepl-ido-form nrepl-ido-ns))
+ (nrepl-ido-read-var-handler ido-callback (current-buffer))
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session)))
+
+(defun nrepl-read-symbol-name (prompt callback &optional query)
+ "Either read a symbol name using PROMPT or choose the one at point.
+Use CALLBACK as the ido read var callback.
+The user is prompted with PROMPT if a prefix argument is in effect,
+if there is no symbol at point, or if QUERY is non-nil."
+ (let ((symbol-name (nrepl-symbol-at-point)))
+ (cond ((not (or current-prefix-arg query (not symbol-name)))
+ (funcall callback symbol-name))
+ (ido-mode (nrepl-ido-read-var nrepl-buffer-ns callback))
+ (t (funcall callback (read-from-minibuffer prompt symbol-name))))))
+
+(defun nrepl-doc-handler (symbol)
+ "Create a handler to lookup documentation for SYMBOL."
+ (let ((form (format "(clojure.repl/doc %s)" symbol))
+ (doc-buffer (nrepl-popup-buffer nrepl-doc-buffer t)))
+ (nrepl-send-string form
+ (nrepl-popup-eval-out-handler doc-buffer)
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+
+(defun nrepl-doc (query)
+ "Open a window with the docstring for the given QUERY.
+Defaults to the symbol at point. With prefix arg or no symbol
+under point, prompts for a var."
+ (interactive "P")
+ (nrepl-read-symbol-name "Symbol: " 'nrepl-doc-handler query))
+
+(defun nrepl-src-handler (symbol)
+ "Create a handler to lookup source for SYMBOL."
+ (let ((form (format "(clojure.repl/source %s)" symbol))
+ (src-buffer (nrepl-popup-buffer nrepl-src-buffer t)))
+ (with-current-buffer src-buffer
+ (clojure-mode)
+ (nrepl-popup-buffer-mode +1))
+ (nrepl-send-string form
+ (nrepl-popup-eval-out-handler src-buffer)
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+
+(defun nrepl-src (query)
+ "Open a window with the source for the given QUERY.
+Defaults to the symbol at point. With prefix arg or no symbol
+under point, prompts for a var."
+ (interactive "P")
+ (nrepl-read-symbol-name "Symbol: " 'nrepl-src-handler query))
+
+;; TODO: implement reloading ns
+(defun nrepl-eval-load-file (form)
+ "Load FORM."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form (nrepl-interactive-eval-handler buffer))))
+
+(defun nrepl-file-string (file)
+ "Read the contents of a FILE and return as a string."
+ (with-current-buffer (find-file-noselect file)
+ (buffer-string)))
+
+(defun nrepl-load-file-op (filename)
+ "Send \"load-file\" op for FILENAME."
+ (nrepl-send-load-file (nrepl-file-string filename)
+ filename
+ (file-name-nondirectory filename)))
+
+(defun nrepl-load-file-core (filename)
+ "Load the Clojure file FILENAME."
+ (let ((fn (replace-regexp-in-string
+ "\\\\" "\\\\\\\\"
+ (convert-standard-filename (expand-file-name filename)))))
+ (nrepl-eval-load-file
+ (format "(clojure.core/load-file \"%s\")\n(in-ns '%s)\n"
+ fn (nrepl-find-ns)))))
+
+(defun nrepl-dispatch-load-file (filename)
+ "Dispatch the load file operation for FILENAME."
+ (if (nrepl-op-supported-p "load-file")
+ (nrepl-load-file-op filename)
+ (nrepl-load-file-core filename)))
+
+(defun nrepl-load-file (filename)
+ "Load the Clojure file FILENAME."
+ (interactive (list
+ (read-file-name "Load file: " nil nil
+ nil (if (buffer-file-name)
+ (file-name-nondirectory
+ (buffer-file-name))))))
+ (remove-overlays (point-min) (point-max) 'nrepl-note-p t)
+ (nrepl-dispatch-load-file filename)
+ (message "Loading %s..." filename))
+
+(defun nrepl-load-current-buffer ()
+ "Load current buffer's file."
+ (interactive)
+ (check-parens)
+ (unless buffer-file-name
+ (error "Buffer %s is not associated with a file" (buffer-name)))
+ (when (and (buffer-modified-p)
+ (y-or-n-p (format "Save file %s? " (buffer-file-name))))
+ (save-buffer))
+ (nrepl-load-file (buffer-file-name)))
+
+;;; selector
+(defvar nrepl-selector-methods nil
+ "List of buffer-selection methods for the `nrepl-select' command.
+Each element is a list (KEY DESCRIPTION FUNCTION).
+DESCRIPTION is a one-line description of what the key selects.")
+
+(defvar nrepl-selector-other-window nil
+ "If non-nil use `switch-to-buffer-other-window'.")
+
+(defun nrepl-selector (&optional other-window)
+ "Select a new buffer by type, indicated by a single character.
+The user is prompted for a single character indicating the method by
+which to choose a new buffer. The `?' character describes then
+available methods. OTHER-WINDOW provides an optional target.
+
+See `def-nrepl-selector-method' for defining new methods."
+ (interactive)
+ (message "Select [%s]: "
+ (apply #'string (mapcar #'car nrepl-selector-methods)))
+ (let* ((nrepl-selector-other-window other-window)
+ (ch (save-window-excursion
+ (select-window (minibuffer-window))
+ (read-char)))
+ (method (cl-find ch nrepl-selector-methods :key #'car)))
+ (cond (method
+ (funcall (cl-caddr method)))
+ (t
+ (message "No method for character: ?\\%c" ch)
+ (ding)
+ (sleep-for 1)
+ (discard-input)
+ (nrepl-selector)))))
+
+(defmacro def-nrepl-selector-method (key description &rest body)
+ "Define a new `nrepl-select' buffer selection method.
+
+KEY is the key the user will enter to choose this method.
+
+DESCRIPTION is a one-line sentence describing how the method
+selects a buffer.
+
+BODY is a series of forms which are evaluated when the selector
+is chosen. The returned buffer is selected with
+`switch-to-buffer'."
+ (let ((method `(lambda ()
+ (let ((buffer (progn ,@body)))
+ (cond ((not (get-buffer buffer))
+ (message "No such buffer: %S" buffer)
+ (ding))
+ ((get-buffer-window buffer)
+ (select-window (get-buffer-window buffer)))
+ (nrepl-selector-other-window
+ (switch-to-buffer-other-window buffer))
+ (t
+ (switch-to-buffer buffer)))))))
+ `(setq nrepl-selector-methods
+ (cl-sort (cons (list ,key ,description ,method)
+ (cl-remove ,key nrepl-selector-methods :key #'car))
+ #'< :key #'car))))
+
+(def-nrepl-selector-method ?? "Selector help buffer."
+ (ignore-errors (kill-buffer "*Select Help*"))
+ (with-current-buffer (get-buffer-create "*Select Help*")
+ (insert "Select Methods:\n\n")
+ (loop for (key line nil) in nrepl-selector-methods
+ do (insert (format "%c:\t%s\n" key line)))
+ (goto-char (point-min))
+ (help-mode)
+ (display-buffer (current-buffer) t))
+ (nrepl-selector)
+ (current-buffer))
+
+(pushnew (list ?4 "Select in other window" (lambda () (nrepl-selector t)))
+ nrepl-selector-methods :key #'car)
+
+(def-nrepl-selector-method ?q "Abort."
+ (top-level))
+
+(def-nrepl-selector-method ?r
+ "Current *nrepl* buffer."
+ (nrepl-find-or-create-repl-buffer))
+
+(def-nrepl-selector-method ?n
+ "NREPL connections buffer."
+ (nrepl-connection-browser)
+ nrepl--connection-browser-buffer-name)
+
+(def-nrepl-selector-method ?v
+ "*nrepl-events* buffer."
+ nrepl-event-buffer-name)
+
+;; TBD --
+;;(def-nrepl-selector-method ?s
+;; "Cycle to the next Clojure connection."
+;; (nrepl-cycle-connections)
+;; (concat "*nrepl "
+;; (nrepl-connection-name (nrepl-current-connection))
+;; "*"))
+
+(defun nrepl-recently-visited-buffer (mode)
+ "Return the most recently visited buffer whose `major-mode' is MODE.
+Only considers buffers that are not already visible."
+ (loop for buffer in (buffer-list)
+ when (and (with-current-buffer buffer (eq major-mode mode))
+ (not (string-match "^ " (buffer-name buffer)))
+ (null (get-buffer-window buffer 'visible)))
+ return buffer
+ finally (error "Can't find unshown buffer in %S" mode)))
+
+(def-nrepl-selector-method ?c
+ "most recently visited clojure-mode buffer."
+ (nrepl-recently-visited-buffer 'clojure-mode))
+
+(def-nrepl-selector-method ?e
+ "most recently visited emacs-lisp-mode buffer."
+ (nrepl-recently-visited-buffer 'emacs-lisp-mode))
+
+;;; interrupt
+(defun nrepl-interrupt-handler (buffer)
+ "Create an interrupt response handler for BUFFER."
+ (nrepl-make-response-handler buffer nil nil nil nil))
+
+(defun nrepl-hash-keys (hashtable)
+ "Return a list of keys in HASHTABLE."
+ (let ((keys '()))
+ (maphash (lambda (k v) (setq keys (cons k keys))) hashtable)
+ keys))
+
+(defun nrepl-interrupt ()
+ "Interrupt any pending evaluations."
+ (interactive)
+ (let ((pending-request-ids (nrepl-hash-keys nrepl-requests)))
+ (dolist (request-id pending-request-ids)
+ (nrepl-send-interrupt request-id (nrepl-interrupt-handler (current-buffer))))))
+
+;;; server
+(defun nrepl-server-filter (process output)
+ "Process nREPL server output from PROCESS contained in OUTPUT."
+ (with-current-buffer (process-buffer process)
+ (save-excursion
+ (goto-char (point-max))
+ (insert output)))
+ (when (string-match "nREPL server started on port \\([0-9]+\\)" output)
+ (let ((port (string-to-number (match-string 1 output))))
+ (message (format "nREPL server started on %s" port))
+ (with-current-buffer (process-buffer process)
+ (let ((nrepl-process (nrepl-connect "localhost" port)))
+ (setq nrepl-connection-buffer
+ (buffer-name (process-buffer nrepl-process)))
+ (with-current-buffer (process-buffer nrepl-process)
+ (setq nrepl-server-buffer
+ (buffer-name (process-buffer process))
+ nrepl-project-dir
+ (buffer-local-value
+ 'nrepl-project-dir (process-buffer process)))))))))
+
+(defun nrepl-server-sentinel (process event)
+ "Handle nREPL server PROCESS EVENT."
+ (let* ((b (process-buffer process))
+ (connection-buffer (buffer-local-value 'nrepl-connection-buffer b))
+ (problem (if (and b (buffer-live-p b))
+ (with-current-buffer b
+ (buffer-substring (point-min) (point-max)))
+ "")))
+ (when b
+ (kill-buffer b))
+ (cond
+ ((string-match "^killed" event)
+ nil)
+ ((string-match "^hangup" event)
+ (when connection-buffer
+ (nrepl-close connection-buffer)))
+ ((string-match "Wrong number of arguments to repl task" problem)
+ (error "Leiningen 2.x is required by nREPL.el"))
+ (t (error "Could not start nREPL server: %s" problem)))))
+
+;;;###autoload
+(defun nrepl-enable-on-existing-clojure-buffers ()
+ "Enable interaction mode on existing Clojure buffers.
+See command `nrepl-interaction-mode'."
+ (interactive)
+ (add-hook 'clojure-mode-hook 'clojure-enable-nrepl)
+ (save-window-excursion
+ (dolist (buffer (buffer-list))
+ (with-current-buffer buffer
+ (when (eq major-mode 'clojure-mode)
+ (clojure-enable-nrepl))))))
+
+;;;###autoload
+(defun nrepl-disable-on-existing-clojure-buffers ()
+ "Disable interaction mode on existing Clojure buffers.
+See command `nrepl-interaction-mode'."
+ (interactive)
+ (save-window-excursion
+ (dolist (buffer (buffer-list))
+ (with-current-buffer buffer
+ (when (eq major-mode 'clojure-mode)
+ (setq nrepl-buffer-ns "user")
+ (clojure-disable-nrepl))))))
+
+(defun nrepl-possibly-disable-on-existing-clojure-buffers ()
+ "If not connected, disable nrepl interaction mode on existing Clojure buffers."
+ (when (not (nrepl-current-connection-buffer))
+ (nrepl-disable-on-existing-clojure-buffers)))
+
+;;;###autoload
+(defun nrepl-jack-in (&optional prompt-project)
+ "Start a nREPL server for the current project and connect to it.
+If PROMPT-PROJECT is t, then prompt for the project for which to
+start the server."
+ (interactive "P")
+ (setq nrepl-current-clojure-buffer (current-buffer))
+ (lexical-let* ((project (when prompt-project
+ (ido-read-directory-name "Project: ")))
+ (project-dir (nrepl-project-directory-for
+ (or project (nrepl-current-dir)))))
+ (when (nrepl-check-for-repl-buffer nil project-dir)
+ (let* ((nrepl-project-dir project-dir)
+ (cmd (if project
+ (format "cd %s && %s" project nrepl-server-command)
+ nrepl-server-command))
+ (process (start-process-shell-command
+ "nrepl-server"
+ (generate-new-buffer-name (nrepl-server-buffer-name))
+ cmd)))
+ (set-process-filter process 'nrepl-server-filter)
+ (set-process-sentinel process 'nrepl-server-sentinel)
+ (set-process-coding-system process 'utf-8-unix 'utf-8-unix)
+ (with-current-buffer (process-buffer process)
+ (setq nrepl-project-dir project-dir))
+ (message "Starting nREPL server...")))))
+
+(defun nrepl-current-dir ()
+ "Return the directory of the current buffer."
+ (lexical-let ((file-name (buffer-file-name (current-buffer))))
+ (or (when file-name
+ (file-name-directory file-name))
+ list-buffers-directory)))
+
+(defun nrepl-project-directory-for (dir-name)
+ "Return the project directory for the specified DIR-NAME."
+ (when dir-name
+ (locate-dominating-file dir-name "project.clj")))
+
+(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)
+ (lexical-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 buffer already exists. Do you really want to create a new one? ")
+ t))
+
+(defun nrepl--close-buffer (buffer)
+ "Close the nrepl BUFFER."
+ (when (get-buffer-process buffer)
+ (delete-process (get-buffer-process buffer)))
+ (when (get-buffer buffer)
+ (kill-buffer buffer)))
+
+(defun nrepl-close-ancilliary-buffers ()
+ "Close buffers that are shared across connections."
+ (interactive)
+ (dolist (buf-name `(,nrepl-error-buffer
+ ,nrepl-doc-buffer
+ ,nrepl-src-buffer
+ ,nrepl-macroexpansion-buffer
+ ,nrepl-event-buffer-name))
+ (nrepl--close-buffer buf-name)))
+
+(defun nrepl-close (connection-buffer)
+ "Close the nrepl connection for CONNECTION-BUFFER."
+ (interactive (list (nrepl-current-connection-buffer)))
+ (nrepl--close-connection-buffer connection-buffer)
+ (nrepl-possibly-disable-on-existing-clojure-buffers)
+ (nrepl--connections-refresh))
+
+(defun nrepl-quit ()
+ "Quit the nrepl server."
+ (interactive)
+ (when (y-or-n-p "Are you sure you want to quit nrepl?")
+ (dolist (connection nrepl-connection-list)
+ (when connection
+ (nrepl-close connection)))
+ (message "All active nrepl connections were closed")
+ (nrepl-close-ancilliary-buffers)))
+
+(defun nrepl-restart (&optional prompt-project)
+ "Quit nrepl and restart it.
+If PROMPT-PROJECT is t, then prompt for the project in which to
+restart the server."
+ (interactive)
+ (nrepl-quit)
+ (nrepl-jack-in current-prefix-arg))
+
+;;; client
+(defun nrepl-op-supported-p (op)
+ "Return t iff the given operation OP is supported by nREPL server."
+ (with-current-buffer (nrepl-current-connection-buffer)
+ (if (and nrepl-ops (assoc op nrepl-ops))
+ t)))
+
+(defun nrepl-describe-handler (process-buffer)
+ "Return a handler to describe into PROCESS-BUFFER."
+ (lexical-let ((buffer process-buffer))
+ (lambda (response)
+ (nrepl-dbind-response response (ops)
+ (cond (ops
+ (with-current-buffer buffer
+ (setq nrepl-ops ops))))))))
+
+(defun nrepl-describe-session (process)
+ "Peform describe for the given server PROCESS."
+ (let ((buffer (process-buffer process)))
+ (nrepl-send-request (list "op" "describe")
+ (nrepl-describe-handler buffer))))
+
+(defun nrepl-setup-default-namespaces (process)
+ "Setup default namespaces for PROCESS."
+ (let ((buffer (process-buffer process)))
+ (with-current-buffer buffer
+ (nrepl-send-string
+ nrepl-repl-requires-sexp
+ (nrepl-make-response-handler
+ buffer nil
+ (lambda (buffer out) (message out))
+ (lambda (buffer err) (message err))
+ nil)
+ nrepl-buffer-ns
+ nrepl-tooling-session))))
+
+(defun nrepl-new-tooling-session-handler (process)
+ "Create a new tooling session handler for PROCESS."
+ (lexical-let ((process process))
+ (lambda (response)
+ (nrepl-dbind-response response (id new-session)
+ (cond (new-session
+ (with-current-buffer (process-buffer process)
+ (setq nrepl-tooling-session new-session)
+ (remhash id nrepl-requests)
+ (nrepl-setup-default-namespaces process))))))))
+
+(defun nrepl-new-session-handler (process no-repl-p)
+ "Create a new session handler for PROCESS.
+When NO-REPL-P is truthy, suppress creation of a REPL buffer."
+ (lexical-let ((process process)
+ (no-repl-p no-repl-p))
+ (lambda (response)
+ (nrepl-dbind-response response (id new-session)
+ (remhash id nrepl-requests)
+ (cond (new-session
+ (lexical-let ((connection-buffer (process-buffer process)))
+ (message "Connected. %s" (nrepl-random-words-of-inspiration))
+ (setq nrepl-session new-session
+ nrepl-connection-buffer connection-buffer)
+ (unless no-repl-p
+ (nrepl-make-repl process)
+ (nrepl-make-repl-connection-default connection-buffer))
+ (run-hooks 'nrepl-connected-hook))))))))
+
+(defun nrepl-init-client-sessions (process no-repl-p)
+ "Initialize client sessions for PROCESS.
+When NO-REPL-P is truthy, suppress creation of a REPL buffer."
+ (nrepl-create-client-session (nrepl-new-session-handler process no-repl-p))
+ (nrepl-create-client-session (nrepl-new-tooling-session-handler process)))
+
+(defun nrepl-connect (host port &optional no-repl-p)
+ "Connect to a running nREPL server running on HOST and PORT.
+When NO-REPL-P is truthy, suppress creation of a REPL buffer."
+ (message "Connecting to nREPL on %s:%s..." host port)
+ (let* ((nrepl-endpoint `(,host ,port))
+ (process (open-network-stream "nrepl"
+ (nrepl-make-connection-buffer) host
+ port)))
+ (set-process-filter process 'nrepl-net-filter)
+ (set-process-sentinel process 'nrepl-sentinel)
+ (set-process-coding-system process 'utf-8-unix 'utf-8-unix)
+ (with-current-buffer (process-buffer process)
+ (setq nrepl-endpoint `(,host ,port)))
+ (let ((nrepl-connection-dispatch (buffer-name (process-buffer process))))
+ (nrepl-init-client-sessions process no-repl-p)
+ (nrepl-describe-session process))
+ process))
+
+(defun nrepl-default-port ()
+ "Attempt to read port from target/repl-port.
+Falls back to `nrepl-port' if not found."
+ (let* ((dir (nrepl-project-directory-for (nrepl-current-dir)))
+ (f (expand-file-name "target/repl-port" dir))
+ (port (when (file-exists-p f)
+ (with-temp-buffer
+ (insert-file-contents f)
+ (buffer-string)))))
+ (or port nrepl-port)))
+
+;;;###autoload
+(add-hook 'nrepl-connected-hook 'nrepl-enable-on-existing-clojure-buffers)
+(add-hook 'nrepl-disconnected-hook
+ 'nrepl-possibly-disable-on-existing-clojure-buffers)
+
+;;;###autoload
+(defun nrepl (host port)
+ "Connect nrepl to HOST and PORT."
+ (interactive (list (read-string "Host: " nrepl-host nil nrepl-host)
+ (string-to-number (let ((port (nrepl-default-port)))
+ (read-string "Port: " port nil port)))))
+ (setq nrepl-current-clojure-buffer (current-buffer))
+ (when (nrepl-check-for-repl-buffer `(,host ,port) nil)
+ (nrepl-connect host port)))
+
+;;;###autoload
+(eval-after-load 'clojure-mode
+ '(progn
+ (define-key clojure-mode-map (kbd "C-c M-j") 'nrepl-jack-in)
+ (define-key clojure-mode-map (kbd "C-c M-c") 'nrepl)))
+
+(provide 'nrepl-client)
+;;; nrepl-client.el ends here