diff --git a/cider-client.el b/cider-client.el index 5d30ea225..5cd7ffca6 100644 --- a/cider-client.el +++ b/cider-client.el @@ -26,8 +26,8 @@ ;;; Code: (require 'spinner) -(require 'ewoc) (require 'nrepl-client) +(require 'cider-connection) (require 'cider-common) (require 'cider-util) (require 'clojure-mode) @@ -36,535 +36,6 @@ (require 'cider-compat) (require 'seq) -;;; Connection Buffer Management - -(defcustom cider-request-dispatch 'dynamic - "Controls the request dispatch mechanism when several connections are present. -Dynamic dispatch tries to infer the connection based on the current project -& currently visited file, while static dispatch simply uses the default -connection. - -Project metadata is attached to connections when they are created with commands -like `cider-jack-in' and `cider-connect'." - :type '(choice (const :tag "dynamic" dynamic) - (const :tag "static" static)) - :group 'cider - :package-version '(cider . "0.10.0")) - -(defcustom cider-connection-message-fn #'cider-random-words-of-inspiration - "The function to use to generate the message displayed on connect. -When set to nil no additional message will be displayed. - -A good alternative to the default is `cider-random-tip'." - :type 'function - :group 'cider - :package-version '(cider . "0.11.0")) - -(defvar cider-connections nil - "A list of connections.") - -(defun cider-connected-p () - "Return t if CIDER is currently connected, nil otherwise." - (not (null (cider-connections)))) - -(defun cider-ensure-connected () - "Ensure there is a cider connection present. -An error is signaled in the absence of a connection." - (unless (cider-connected-p) - (user-error "`%s' needs an active nREPL connection" this-command))) - -(defsubst cider--in-connection-buffer-p () - "Return non-nil if current buffer is connected to a server." - (and (derived-mode-p 'cider-repl-mode) - (process-live-p - (get-buffer-process (current-buffer))))) - -(defun cider-default-connection (&optional no-error) - "The default (fallback) connection to use for nREPL interaction. -When NO-ERROR is non-nil, don't throw an error when no connection has been -found." - (or (car (cider-connections)) - (unless no-error - (error "No nREPL connection buffer")))) - -(defun cider-connections () - "Return the list of connection buffers. -If the list is empty and buffer-local, return the global value." - (or (setq cider-connections - (seq-filter #'buffer-live-p cider-connections)) - (when (local-variable-p 'cider-connections) - (kill-local-variable 'cider-connections) - (seq-filter #'buffer-live-p cider-connections)))) - -(defun cider-repl-buffers () - "Return the list of REPL buffers." - (seq-filter - (lambda (buffer) - (with-current-buffer buffer (derived-mode-p 'cider-repl-mode))) - (buffer-list))) - -(defun cider-make-connection-default (connection-buffer) - "Make the nREPL CONNECTION-BUFFER the default connection. -Moves CONNECTION-BUFFER to the front of variable `cider-connections'." - (interactive (list (if (cider--in-connection-buffer-p) - (current-buffer) - (user-error "Not in a REPL buffer")))) - ;; maintain the connection list in most recently used order - (let ((buf (get-buffer connection-buffer))) - (setq cider-connections - (cons buf (delq buf cider-connections)))) - (cider--connections-refresh)) - -(declare-function cider--close-buffer "cider-interaction") -(defun cider--close-connection-buffer (conn-buffer) - "Close CONN-BUFFER, removing it from variable `cider-connections'. -Also close associated REPL and server buffers." - (let ((buffer (get-buffer conn-buffer)) - (nrepl-messages-buffer (and nrepl-log-messages - (nrepl-messages-buffer conn-buffer)))) - (setq cider-connections - (delq buffer cider-connections)) - (when (buffer-live-p buffer) - (with-current-buffer buffer - (when spinner-current (spinner-stop)) - (when nrepl-tunnel-buffer - (cider--close-buffer nrepl-tunnel-buffer))) - ;; If this is the only (or last) REPL connected to its server, the - ;; kill-process hook will kill the server. - (cider--close-buffer buffer) - (when nrepl-messages-buffer - (kill-buffer nrepl-messages-buffer))))) - - -;;; Current connection logic -(defvar-local cider-repl-type nil - "The type of this REPL buffer, usually either \"clj\" or \"cljs\".") - -(defun cider-find-connection-buffer-for-project-directory (&optional project-directory all-connections) - "Return the most appropriate connection-buffer for the current project. - -By order of preference, this is any connection whose directory matches -`clojure-project-dir', followed by any connection whose directory is nil, -followed by any connection at all. - -If PROJECT-DIRECTORY is provided act on that project instead. - -Only return nil if variable `cider-connections' is empty, -i.e there are no connections. - -If more than one connection satisfy a given level of preference, return the -connection buffer closer to the start of variable `cider-connections'. This is -usally the connection that was more recently created, but the order can be -changed. For instance, the function `cider-make-connection-default' can be -used to move a connection to the head of the list, so that it will take -precedence over other connections associated with the same project. - -If ALL-CONNECTIONS is non-nil, the return value is a list and all matching -connections are returned, instead of just the most recent." - (when-let* ((project-directory (or project-directory - (clojure-project-dir (cider-current-dir)))) - (fn (if all-connections #'seq-filter #'seq-find))) - (or (funcall fn (lambda (conn) - (when-let* ((conn-proj-dir (with-current-buffer conn - nrepl-project-dir))) - (equal (file-truename project-directory) - (file-truename conn-proj-dir)))) - cider-connections) - (funcall fn (lambda (conn) - (with-current-buffer conn - (not nrepl-project-dir))) - cider-connections) - (if all-connections - cider-connections - (car cider-connections))))) - -(defun cider-connection-type-for-buffer (&optional buffer) - "Return the matching connection type (clj or cljs) for BUFFER. -In cljc buffers return \"multi\". This function infers connection -type based on the major mode. See `cider-project-connections-types' for a -list of types of actual connections within a project. BUFFER defaults to -the `current-buffer'." - (with-current-buffer (or buffer (current-buffer)) - (cond - ((derived-mode-p 'clojurescript-mode) "cljs") - ((derived-mode-p 'clojurec-mode) "multi") - ((derived-mode-p 'clojure-mode) "clj") - (cider-repl-type)))) - -(defun cider-project-connections-types () - "Return a list of types of connections within current project." - (let ((connections (cider-find-connection-buffer-for-project-directory nil :all-connections))) - (seq-uniq (seq-map #'cider--connection-type connections)))) - -(defun cider-read-connection (prompt) - "Completing read for connections using PROMPT." - (get-buffer (completing-read prompt (mapcar #'buffer-name (cider-connections))))) - -(defun cider-assoc-project-with-connection (&optional project connection) - "Associate a Clojure PROJECT with an nREPL CONNECTION. - -Useful for connections created using `cider-connect', as for them -such a link cannot be established automatically." - (interactive) - (cider-ensure-connected) - (let ((conn-buf (or connection (cider-read-connection "Connection: "))) - (project-dir (or project (read-directory-name "Project directory: " (clojure-project-dir))))) - (when conn-buf - (with-current-buffer conn-buf - (setq nrepl-project-dir project-dir))))) - -(defun cider-assoc-buffer-with-connection () - "Associate the current buffer with a connection. - -Useful for connections created using `cider-connect', as for them -such a link cannot be established automatically." - (interactive) - (cider-ensure-connected) - (let ((conn (cider-read-connection "Connection: "))) - (when conn - (setq-local cider-connections (list conn))))) - -(defun cider-toggle-buffer-connection (&optional restore-all) - "Toggle the current buffer's connection between Clojure and ClojureScript. - -Default behavior of a cljc buffer is to send eval commands to both Clojure -and ClojureScript. This function sets a local buffer variable to hide one -or the other. Optional argument RESTORE-ALL undo any toggled behavior by -using the default list of connections." - (interactive "P") - (cider-ensure-connected) - (if restore-all - (progn - (kill-local-variable 'cider-connections) - (let ((types (mapcar #'cider--connection-type (cider-connections)))) - (message (format "CIDER connections available: %s" types)))) - (let ((current-conn (cider-current-connection)) - (was-local (local-variable-p 'cider-connections)) - (original-connections (cider-connections))) - ;; we set the local variable to eclipse all connections in favor of the - ;; toggled connection. to recover the full list we must remove the - ;; obfuscation - (kill-local-variable 'cider-connections) - (if-let* ((other-conn (cider-other-connection current-conn))) - (progn - (setq-local cider-connections (list other-conn)) - (message "Connection set to %s" (cider--connection-type other-conn))) - (progn - (when was-local - (setq-local cider-connections original-connections)) - (user-error "No other connection available")))))) - -(defun cider-clear-buffer-local-connection () - "Remove association between the current buffer and a connection." - (interactive) - (cider-ensure-connected) - (kill-local-variable 'cider-connections)) - -(defun cider-toggle-request-dispatch () - "Toggle the value of `cider-request-dispatch' between static and dynamic. - -Handy when you're using dynamic dispatch, but you want to quickly force all -evaluation commands to use a particular connection." - (interactive) - (let ((new-value (if (eq cider-request-dispatch 'static) 'dynamic 'static))) - (setq cider-request-dispatch new-value) - (message "Toggled CIDER request dispatch to %s." new-value))) - -(defun cider-current-connection (&optional type) - "Return the REPL buffer relevant for the current Clojure source buffer. -A REPL is relevant if its `nrepl-project-dir' is compatible with the -current directory (see `cider-find-connection-buffer-for-project-directory'). - -When there are multiple relevant connections of the same TYPE, return the -most recently used one. - -If TYPE is provided, it is either \"clj\" or \"cljs\", and only a -connection of that type is returned. If no connections of that TYPE exist, -return nil. - -If TYPE is nil, then connections whose type matches the current file -extension are given preference, but if none exist, any connection is -returned. In this case, only return nil if there are no active connections -at all." - ;; If TYPE was specified, we only return that type (or nil). OW, we prefer - ;; that TYPE, but ultimately allow any type. - (cl-labels ((right-type-p (c type) - (when (or (not type) - (equal type "multi") - (and (buffer-live-p c) - (equal (cider--connection-type c) type))) - c)) - (most-recent-buf (connections type) - (when connections - (seq-find (lambda (c) - (and (member c connections) - (right-type-p c type))) - (buffer-list))))) - (let ((connections (cider-connections))) - (cond - ((not connections) nil) - ;; if you're in a REPL buffer, it's the connection buffer - ((and (derived-mode-p 'cider-repl-mode) (right-type-p (current-buffer) type))) - ((eq cider-request-dispatch 'static) (car connections)) - ((= 1 (length connections)) (right-type-p (car connections) type)) - (t (let ((project-connections (cider-find-connection-buffer-for-project-directory - nil :all-connections)) - (guessed-type (or type (cider-connection-type-for-buffer)))) - (or - ;; cljc - (and (equal guessed-type "multi") - (most-recent-buf project-connections nil)) - ;; clj or cljs - (and guessed-type - (or (most-recent-buf project-connections guessed-type) - (most-recent-buf connections guessed-type))) - ;; when type was not specified or guessed - (most-recent-buf project-connections type) - (most-recent-buf connections type)))))))) - -(defun cider-other-connection (&optional connection) - "Return the first connection of another type than CONNECTION. -Only return connections in the same project or nil. -CONNECTION defaults to `cider-current-connection'." - (when-let* ((connection (or connection (cider-current-connection))) - (connection-type (cider--connection-type connection))) - (cider-current-connection (pcase connection-type - (`"clj" "cljs") - (_ "clj"))))) - -(defvar cider--has-warned-about-bad-repl-type nil) - -(defun cider--guess-cljs-connection () - "Hacky way to find a ClojureScript REPL. -DO NOT USE THIS FUNCTION. -It was written only to be used in `cider-map-connections', as a workaround -to a still-undetermined bug in the state-tracker backend." - (when-let* ((project-connections (cider-find-connection-buffer-for-project-directory - nil :all-connections)) - (cljs-conn - ;; So we have multiple connections. Look for the connection type we - ;; want, prioritizing the current project. - (or (seq-find (lambda (c) (with-current-buffer c (equal cider-repl-type "cljs"))) - project-connections) - (seq-find (lambda (c) (with-current-buffer c (equal cider-repl-type "cljs"))) - (cider-connections))))) - (unless cider--has-warned-about-bad-repl-type - (setq cider--has-warned-about-bad-repl-type t) - (read-key - (concat "The ClojureScript REPL seems to be misbehaving." - (substitute-command-keys - "\nWe have applied a workaround, but please also file a bug report with `\\[cider-report-bug]'.") - "\nPress any key to continue."))) - cljs-conn)) - -(defun cider-map-connections (function which &optional any-mode) - "Call FUNCTION once for each appropriate connection. -The function is called with one argument, the connection buffer. -The appropriate connections are found by inspecting the current buffer. If -the buffer is associated with a .cljc file, BODY will be executed -multiple times. - -WHICH is one of the following keywords identifying which connections to map -over. - :any - Act the connection whose type matches the current buffer. - :clj - Like :any, but signal a `user-error' in `clojurescript-mode' or if - there is no Clojure connection (use this for commands only - supported in Clojure). - :cljs - Like :clj, but demands a ClojureScript connection instead. - :both - In `clojurec-mode' act on both connections, otherwise function - like :any. Obviously, this option might run FUNCTION twice. - -If ANY-MODE is non-nil, :clj and :cljs don't signal errors due to being in -the wrong major mode (they still signal if the desired connection type -doesn't exist). Use this for commands that only apply to a specific -connection but can be invoked from any buffer (like `cider-refresh')." - (cl-labels ((err (msg) (user-error (concat "`%s' " msg) this-command))) - ;; :both in a clj or cljs buffer just means :any. - (let* ((which (if (and (eq which :both) - (not (cider--cljc-buffer-p))) - :any - which)) - (curr - (pcase which - (`:any (let ((type (cider-connection-type-for-buffer))) - (or (cider-current-connection type) - (when (equal type "cljs") - (cider--guess-cljs-connection)) - (err (substitute-command-keys - (format "needs a Clojure%s REPL.\nIf you don't know what that means, you probably need to jack-in (%s)." - (if (equal type "cljs") "Script" "") - (if (equal type "cljs") "`\\[cider-jack-in-clojurescript]'" "`\\[cider-jack-in]'"))))))) - (`:both (or (cider-current-connection) - (err "needs an active REPL connection"))) - (`:clj (cond ((and (not any-mode) - (derived-mode-p 'clojurescript-mode)) - (err "doesn't support ClojureScript")) - ((cider-current-connection "clj")) - ((err "needs a Clojure REPL")))) - (`:cljs (cond ((and (not any-mode) - (eq major-mode 'clojure-mode)) - (err "doesn't support Clojure")) - ((cider-current-connection "cljs")) - ((err "needs a ClojureScript REPL"))))))) - (funcall function curr) - (when (eq which :both) - (when-let* ((other-connection (cider-other-connection curr))) - (funcall function other-connection)))))) - - -;;; Connection Browser -(defvar cider-connections-buffer-mode-map - (let ((map (make-sparse-keymap))) - (define-key map "d" #'cider-connections-make-default) - (define-key map "g" #'cider-connection-browser) - (define-key map "k" #'cider-connections-close-connection) - (define-key map (kbd "RET") #'cider-connections-goto-connection) - (define-key map "?" #'describe-mode) - (define-key map "h" #'describe-mode) - map)) - -(declare-function cider-popup-buffer-mode "cider-popup") -(define-derived-mode cider-connections-buffer-mode cider-popup-buffer-mode - "CIDER Connections" - "CIDER Connections Buffer Mode. -\\{cider-connections-buffer-mode-map} -\\{cider-popup-buffer-mode-map}" - (when cider-special-mode-truncate-lines - (setq-local truncate-lines t))) - -(defvar cider--connection-ewoc) -(defconst cider--connection-browser-buffer-name "*cider-connections*") - -(defun cider-connection-browser () - "Open a browser buffer for nREPL connections." - (interactive) - (if-let* ((buffer (get-buffer cider--connection-browser-buffer-name))) - (progn - (cider--connections-refresh-buffer buffer) - (unless (get-buffer-window buffer) - (select-window (display-buffer buffer)))) - (cider--setup-connection-browser))) - -(defun cider--connections-refresh () - "Refresh the connections buffer, if the buffer exists. -The connections buffer is determined by -`cider--connection-browser-buffer-name'" - (when-let* ((buffer (get-buffer cider--connection-browser-buffer-name))) - (cider--connections-refresh-buffer buffer))) - -(add-hook 'nrepl-disconnected-hook #'cider--connections-refresh) - -(defun cider--connections-refresh-buffer (buffer) - "Refresh the connections BUFFER." - (cider--update-connections-display - (buffer-local-value 'cider--connection-ewoc buffer) - cider-connections)) - -(defun cider--setup-connection-browser () - "Create a browser buffer for nREPL connections." - (with-current-buffer (get-buffer-create cider--connection-browser-buffer-name) - (let ((ewoc (ewoc-create - 'cider--connection-pp - " REPL Host Port Project Type\n"))) - (setq-local cider--connection-ewoc ewoc) - (cider--update-connections-display ewoc cider-connections) - (setq buffer-read-only t) - (cider-connections-buffer-mode) - (display-buffer (current-buffer))))) - -(defun cider-client-name-repl-type (type) - "Return a human-readable name for a connection TYPE. -TYPE can be any of the possible values of `cider-repl-type'." - (pcase type - ("clj" "Clojure") - ("cljs" "ClojureScript") - (_ "Unknown"))) - -(defun cider-project-name (project-dir) - "Extract the project name from PROJECT-DIR." - (if (and project-dir (not (equal project-dir ""))) - (file-name-nondirectory (directory-file-name project-dir)) - "-")) - -(defun cider--connection-pp (connection) - "Print an nREPL CONNECTION to the current buffer." - (let* ((buffer-read-only nil) - (buffer (get-buffer connection)) - (project-name (cider-project-name (buffer-local-value 'nrepl-project-dir buffer))) - (repl-type (cider-client-name-repl-type (buffer-local-value 'cider-repl-type buffer))) - (endpoint (buffer-local-value 'nrepl-endpoint buffer))) - (insert - (format "%s %-30s %-16s %5s %-16s %s" - (if (equal connection (car cider-connections)) "*" " ") - (buffer-name connection) - (car endpoint) - (prin1-to-string (cadr endpoint)) - project-name - repl-type)))) - -(defun cider--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) - (let ((added (seq-difference connections existing))) - (mapc (apply-partially 'ewoc-enter-last ewoc) added) - (save-excursion (ewoc-refresh ewoc))))) - -(defun cider--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." - (let* ((ewoc cider--connection-ewoc) - (node (and ewoc (ewoc-locate ewoc)))) - (when node - (funcall f ewoc (ewoc-data node))))) - -(defun cider-connections-make-default () - "Make default the connection at point in the connection browser." - (interactive) - (save-excursion - (cider--ewoc-apply-at-point #'cider--connections-make-default))) - -(defun cider--connections-make-default (ewoc data) - "Make the connection in EWOC specified by DATA default. -Refreshes EWOC." - (interactive) - (cider-make-connection-default data) - (ewoc-refresh ewoc)) - -(defun cider-connections-close-connection () - "Close connection at point in the connection browser." - (interactive) - (cider--ewoc-apply-at-point #'cider--connections-close-connection)) - -(defun cider--connections-close-connection (ewoc data) - "Close the connection in EWOC specified by DATA." - (cider--close-connection-buffer (get-buffer data)) - (cider--update-connections-display ewoc cider-connections)) - -(defun cider-connections-goto-connection () - "Goto connection at point in the connection browser." - (interactive) - (cider--ewoc-apply-at-point #'cider--connections-goto-connection)) - -(defun cider--connections-goto-connection (_ewoc data) - "Goto the REPL for the connection in _EWOC specified by DATA." - (when (buffer-live-p data) - (select-window (display-buffer data)))) - - -(defun cider-display-connected-message () - "Message displayed on successful connection." - (message - (concat "Connected." - (if cider-connection-message-fn - (format " %s" (funcall cider-connection-message-fn)) - "")))) - -;; TODO: Replace direct usage of such hooks with CIDER hooks, -;; that are connection type independent -(add-hook 'nrepl-connected-hook 'cider-display-connected-message) - ;;; Eval spinner (defcustom cider-eval-spinner-type 'progress-bar @@ -625,34 +96,27 @@ EVAL-BUFFER is the buffer where the spinner was started." (defvar-local cider-buffer-ns nil "Current Clojure namespace of some buffer. - -Useful for special buffers (e.g. REPL, doc buffers) that have to -keep track of a namespace. - -This should never be set in Clojure buffers, as there the namespace -should be extracted from the buffer's ns form.") +Useful for special buffers (e.g. REPL, doc buffers) that have to keep track +of a namespace. This should never be set in Clojure buffers, as there the +namespace should be extracted from the buffer's ns form.") (defun cider-current-ns (&optional no-default) "Return the current ns. The ns is extracted from the ns form for Clojure buffers and from `cider-buffer-ns' for all other buffers. If it's missing, use the current -REPL's ns, otherwise fall back to \"user\". - -When NO-DEFAULT is non-nil, it will return nil instead of \"user\"." +REPL's ns, otherwise fall back to \"user\". When NO-DEFAULT is non-nil, it +will return nil instead of \"user\"." (or cider-buffer-ns (clojure-find-ns) - (when-let* ((repl-buf (cider-current-connection))) - (buffer-local-value 'cider-buffer-ns repl-buf)) + (when-let* ((repl (cider-current-connection))) + (buffer-local-value 'cider-buffer-ns repl)) (if no-default nil "user"))) (defun cider-expected-ns (&optional path) "Return the namespace string matching PATH, or nil if not found. - -PATH is expected to be an absolute file path. -If PATH is nil, use the path to the file backing the current buffer. - -The command falls back to `clojure-expected-ns' in the absence of an -active nREPL connection." +PATH is expected to be an absolute file path. If PATH is nil, use the path +to the file backing the current buffer. The command falls back to +`clojure-expected-ns' in the absence of an active nREPL connection." (if (cider-connected-p) (let* ((path (or path (file-truename (buffer-file-name)))) (relpath (thread-last (cider-sync-request:classpath) @@ -672,9 +136,9 @@ active nREPL connection." (clojure-expected-ns path))) (clojure-expected-ns path))) -(defun cider-nrepl-op-supported-p (op) - "Check whether the current connection supports the nREPL middleware OP." - (nrepl-op-supported-p op (cider-current-connection))) +(defun cider-nrepl-op-supported-p (op &optional connection) + "Check whether the CONNECTION supports the nREPL middleware OP." + (nrepl-op-supported-p op (or connection (cider-current-connection)))) (defvar cider-version) (defun cider-ensure-op-supported (op) @@ -686,11 +150,9 @@ Signal an error if it is not supported." (defun cider-nrepl-send-request (request callback &optional connection) "Send REQUEST and register response handler CALLBACK. REQUEST is a pair list of the form (\"op\" \"operation\" \"par1-name\" -\"par1\" ... ). + \"par1\" ... ). If CONNECTION is provided dispatch to that connection instead of -the current connection. - -Return the id of the sent message." +the current connection. Return the id of the sent message." (nrepl-send-request request callback (or connection (cider-current-connection)))) (defun cider-nrepl-send-sync-request (request &optional connection abort-on-input) @@ -704,11 +166,10 @@ interface." (or connection (cider-current-connection)) abort-on-input)) -(defun cider-nrepl-send-unhandled-request (request) - "Send REQUEST to the nREPL server and ignore any responses. -Immediately mark the REQUEST as done. -Return the id of the sent message." - (let* ((conn (cider-current-connection)) +(defun cider-nrepl-send-unhandled-request (request &optional connection) + "Send REQUEST to the nREPL CONNECTION and ignore any responses. +Immediately mark the REQUEST as done. Return the id of the sent message." + (let* ((conn (or connection (cider-current-connection))) (id (nrepl-send-request request #'ignore conn))) (with-current-buffer conn (nrepl--mark-id-completed id)) @@ -732,22 +193,20 @@ buffer, defaults to (cider-current-connection)." (defun cider-nrepl-sync-request:eval (input &optional connection ns) "Send the INPUT to the nREPL CONNECTION synchronously. If NS is non-nil, include it in the eval request." - (nrepl-sync-request:eval input - (or connection (cider-current-connection)) - ns)) + (nrepl-sync-request:eval input (or connection (cider-current-connection)) ns)) (defcustom cider-pprint-fn 'pprint "Sets the function to use when pretty-printing evaluation results. The value must be one of the following symbols: - `pprint' - to use \\=`clojure.pprint/pprint\\=` +`pprint' - to use \\=`clojure.pprint/pprint\\=` - `fipp' - to use the Fast Idiomatic Pretty Printer, approximately 5-10x - faster than \\=`clojure.core/pprint\\=` (this is the default) +`fipp' - to use the Fast Idiomatic Pretty Printer, approximately 5-10x +faster than \\=`clojure.core/pprint\\=` (this is the default) - `puget' - to use Puget, which provides canonical serialization of data on - top of fipp, but at a slight performance cost +`puget' - to use Puget, which provides canonical serialization of data on +top of fipp, but at a slight performance cost Alternatively, can be the namespace-qualified name of a Clojure function of one argument. If the function cannot be resolved, an exception will be @@ -784,33 +243,29 @@ result, and is included in the request if non-nil." "Plist to be appended to an eval request to make it use content-types." '("content-type" "true")) -(defun cider-tooling-eval (input callback &optional ns) - "Send the request INPUT and register the CALLBACK as the response handler. -NS specifies the namespace in which to evaluate the request. - -Requests evaluated in the tooling nREPL session don't affect the -thread-local bindings of the primary eval nREPL session (e.g. this is not -going to clobber *1/2/3)." +(defun cider-tooling-eval (input callback &optional ns connection) + "Send the request INPUT to CONNECTION and register the CALLBACK. +NS specifies the namespace in which to evaluate the request. Requests +evaluated in the tooling nREPL session don't affect the thread-local +bindings of the primary eval nREPL session (e.g. this is not going to +clobber *1/2/3)." ;; namespace forms are always evaluated in the "user" namespace (nrepl-request:eval input callback - (cider-current-connection) - ns nil nil nil t ; tooling - )) - -(defun cider-sync-tooling-eval (input &optional ns) - "Send the request INPUT and evaluate in synchronously. -NS specifies the namespace in which to evaluate the request. - -Requests evaluated in the tooling nREPL session don't affect the -thread-local bindings of the primary eval nREPL session (e.g. this is not -going to clobber *1/2/3)." + (or connection (cider-current-connection)) + ns nil nil nil 'tooling)) + +(defun cider-sync-tooling-eval (input &optional ns connection) + "Send the request INPUT to CONNECTION and evaluate in synchronously. +NS specifies the namespace in which to evaluate the request. Requests +evaluated in the tooling nREPL session don't affect the thread-local +bindings of the primary eval nREPL session (e.g. this is not going to +clobber *1/2/3)." ;; namespace forms are always evaluated in the "user" namespace (nrepl-sync-request:eval input - (cider-current-connection) + (or connection (cider-current-connection)) ns - t ; tooling - )) + 'tooling)) ;; TODO: Add some unit tests and pretty those two functions up. ;; FIXME: Currently that's broken for group-id with multiple segments (e.g. org.clojure/clojure) @@ -826,7 +281,6 @@ going to clobber *1/2/3)." (defun cider-library-present-p (lib) "Check whether LIB is present on the classpath. - The library is a string of the format \"group-id/artifact-id\"." (let* ((lib (split-string lib "/")) (group-id (car lib)) @@ -837,14 +291,11 @@ The library is a string of the format \"group-id/artifact-id\"." (and (equal group-id g) (equal artifact-id a)))) (cider-classpath-libs)))) -(defalias 'cider-current-repl-buffer #'cider-current-connection - "The current REPL buffer. -Return the REPL buffer given by `cider-current-connection'.") - (declare-function cider-interrupt-handler "cider-interaction") (defun cider-interrupt () "Interrupt any pending evaluations." (interactive) + ;; FIXME: does this work correctly in cljc files? (with-current-buffer (cider-current-connection) (let ((pending-request-ids (cider-util--hash-keys nrepl-pending-requests))) (dolist (request-id pending-request-ids) @@ -853,24 +304,20 @@ Return the REPL buffer given by `cider-current-connection'.") (cider-interrupt-handler (current-buffer)) (cider-current-connection)))))) -(defun cider-current-session () +(defun cider-nrepl-eval-session () "Return the eval nREPL session id of the current connection." - (cider-session-for-connection (cider-current-connection))) - -(defun cider-session-for-connection (connection) - "Create a CIDER session for CONNECTION." - (with-current-buffer connection + (with-current-buffer (cider-current-connection) nrepl-session)) -(defun cider-current-messages-buffer () - "The nREPL messages buffer, matching the current connection." - (nrepl-messages-buffer (cider-current-connection))) - -(defun cider-current-tooling-session () +(defun cider-nrepl-tooling-session () "Return the tooling nREPL session id of the current connection." (with-current-buffer (cider-current-connection) nrepl-tooling-session)) +(defun cider-current-messages-buffer () + "The nREPL messages buffer, matching the current connection." + (nrepl-messages-buffer (cider-current-connection))) + (defun cider--var-choice (var-info) "Prompt to choose from among multiple VAR-INFO candidates, if required. This is needed only when the symbol queried is an unqualified host platform @@ -917,7 +364,6 @@ Display the results in a different window." (defun cider-find-var (&optional arg var line) "Find definition for VAR at LINE. - Prompt according to prefix ARG and `cider-prompt-for-symbol'. A single or double prefix argument inverts the meaning of `cider-prompt-for-symbol'. A prefix of `-` or a double prefix argument causes @@ -940,10 +386,8 @@ thing at point." (defun cider-request:load-file (file-contents file-path file-name &optional connection callback) "Perform the nREPL \"load-file\" op. FILE-CONTENTS, FILE-PATH and FILE-NAME are details of the file to be -loaded. - -If CONNECTION is nil, use `cider-current-connection'. -If CALLBACK is nil, use `cider-load-file-handler'." +loaded. If CONNECTION is nil, use `cider-current-connection'. If CALLBACK +is nil, use `cider-load-file-handler'." (cider-nrepl-send-request `("op" "load-file" "file" ,file-contents "file-path" ,file-path @@ -959,11 +403,11 @@ If CALLBACK is nil, use `cider-load-file-handler'." '("^cider.nrepl" "^refactor-nrepl" "^clojure.tools.nrepl") "List of regexps used to filter out some vars/symbols/namespaces. When nil, nothing is filtered out. Otherwise, all namespaces matching any -regexp from this list are dropped out of the \"ns-list\" op. -Also, \"apropos\" won't include vars from such namespaces. -This list is passed on to the nREPL middleware without any pre-processing. -So the regexps have to be in Clojure format (with twice the number of -backslashes) and not Emacs Lisp." +regexp from this list are dropped out of the \"ns-list\" op. Also, +\"apropos\" won't include vars from such namespaces. This list is passed +on to the nREPL middleware without any pre-processing. So the regexps have +to be in Clojure format (with twice the number of backslashes) and not +Emacs Lisp." :type '(repeat string) :safe #'listp :group 'cider @@ -1007,7 +451,7 @@ CONTEXT represents a completion context for compliment." (defun cider-sync-request:complete-flush-caches () "Send \"complete-flush-caches\" op to flush Compliment's caches." (cider-nrepl-send-sync-request (list "op" "complete-flush-caches" - "session" (cider-current-session)) + "session" (cider-nrepl-eval-session)) 'abort-on-input)) (defun cider-sync-request:info (symbol &optional class member) @@ -1046,7 +490,6 @@ CONTEXT represents a completion context for compliment." (defun cider-sync-request:spec-list (&optional filter-regex) "Get a list of the available specs in the registry. - Optional argument FILTER-REGEX filters specs. By default, all specs are returned." (setq filter-regex (or filter-regex "")) @@ -1112,7 +555,6 @@ returned." (defun cider-sync-request:resources-list () "Return a list of all resources on the classpath. - The result entries are relative to the classpath." (when-let* ((resources (thread-first '("op" "resources-list") (cider-nrepl-send-sync-request) @@ -1139,136 +581,6 @@ The result entries are relative to the classpath." (error (car (split-string err "\n")))) (nrepl-dict-get response "formatted-edn"))) - -;;; Connection info -(defun cider--java-version () - "Retrieve the underlying connection's Java version." - (with-current-buffer (cider-current-connection "clj") - (when nrepl-versions - (thread-first nrepl-versions - (nrepl-dict-get "java") - (nrepl-dict-get "version-string"))))) - -(defun cider--clojure-version () - "Retrieve the underlying connection's Clojure version." - (with-current-buffer (cider-current-connection "clj") - (when nrepl-versions - (thread-first nrepl-versions - (nrepl-dict-get "clojure") - (nrepl-dict-get "version-string"))))) - -(defun cider--nrepl-version () - "Retrieve the underlying connection's nREPL version." - (with-current-buffer (cider-current-connection "clj") - (when nrepl-versions - (thread-first nrepl-versions - (nrepl-dict-get "nrepl") - (nrepl-dict-get "version-string"))))) - -(defun cider--connection-info (connection-buffer) - "Return info about CONNECTION-BUFFER. - -Info contains project name, current REPL namespace, host:port -endpoint and Clojure version." - (with-current-buffer connection-buffer - (format "%s%s@%s:%s (Java %s, Clojure %s, nREPL %s)" - (upcase (concat cider-repl-type " ")) - (or (cider--project-name nrepl-project-dir) "") - (car nrepl-endpoint) - (cadr nrepl-endpoint) - (cider--java-version) - (cider--clojure-version) - (cider--nrepl-version)))) - -(defun cider--connection-properties (conn-buffer) - "Extract the essential properties of CONN-BUFFER." - (with-current-buffer conn-buffer - (list - :type cider-repl-type - :host (car nrepl-endpoint) - :port (cadr nrepl-endpoint) - :project-dir nrepl-project-dir))) - -(defun cider--connection-type (conn-buffer) - "Get CONN-BUFFER's type. - -Return value matches `cider-repl-type'." - (plist-get (cider--connection-properties conn-buffer) :type)) - -(defun cider--connection-host (conn-buffer) - "Get CONN-BUFFER's host." - (plist-get (cider--connection-properties conn-buffer) :host)) - -(defun cider--connection-port (conn-buffer) - "Get CONN-BUFFER's port." - (plist-get (cider--connection-properties conn-buffer) :port)) - -(defun cider--connection-project-dir (conn-buffer) - "Get CONN-BUFFER's project dir." - (plist-get (cider--connection-properties conn-buffer) :project-dir)) - -(defun cider-display-connection-info (&optional show-default) - "Display information about the current connection. - -With a prefix argument SHOW-DEFAULT it will display info about the -default connection." - (interactive "P") - (message "%s" (cider--connection-info (if show-default - (cider-default-connection) - (cider-current-connection))))) - -(defun cider-rotate-default-connection () - "Rotate and display the default nREPL connection." - (interactive) - (cider-ensure-connected) - (if (= (length (cider-connections)) 1) - (user-error "There's just a single active nREPL connection") - (setq cider-connections - (append (cdr cider-connections) - (list (car cider-connections)))) - (message "Default nREPL connection: %s" - (cider--connection-info (car cider-connections))))) - - -(declare-function cider-connect "cider") -(defun cider-replicate-connection (&optional conn) - "Establish a new connection based on an existing connection. -The new connection will use the same host and port. -If CONN is not provided the user will be prompted to select a connection." - (interactive) - (let* ((conn (or conn (cider-read-connection "Select connection to replicate: "))) - (host (cider--connection-host conn)) - (port (cider--connection-port conn)) - (project-dir (cider--connection-project-dir conn))) - (cider-connect host port project-dir))) - -(defun cider-extract-designation-from-current-repl-buffer () - "Extract the designation from the cider repl buffer name." - (let ((repl-buffer-name (buffer-name (cider-current-repl-buffer))) - (template (split-string nrepl-repl-buffer-name-template "%s"))) - (string-match (format "^%s\\(.*\\)%s" - (regexp-quote (concat (car template) nrepl-buffer-name-separator)) - (regexp-quote (cadr template))) - repl-buffer-name) - (or (match-string 1 repl-buffer-name) ""))) - -(defun cider-change-buffers-designation (designation) - "Change the DESIGNATION in cider buffer names. -Buffer names changed are cider-repl and nrepl-server." - (interactive (list (read-string (format "Change CIDER buffer designation from '%s': " - (cider-extract-designation-from-current-repl-buffer))))) - (cider-ensure-connected) - (let ((new-repl-buffer-name (nrepl-format-buffer-name-template - nrepl-repl-buffer-name-template designation))) - (with-current-buffer (cider-current-repl-buffer) - (rename-buffer new-repl-buffer-name) - (when nrepl-server-buffer - (let ((new-server-buffer-name (nrepl-format-buffer-name-template - nrepl-server-buffer-name-template designation))) - (with-current-buffer nrepl-server-buffer - (rename-buffer new-server-buffer-name))))) - (message "CIDER buffer designation changed to: %s" designation))) - (provide 'cider-client) ;;; cider-client.el ends here diff --git a/cider-connection.el b/cider-connection.el new file mode 100644 index 000000000..22395344d --- /dev/null +++ b/cider-connection.el @@ -0,0 +1,402 @@ +;;; cider-connection.el --- Connection management for CIDER -*- lexical-binding: t -*- +;; +;; Copyright © 2018 Bozhidar Batsov, Artur Malabarba, Vitalie Spinu and CIDER contributors +;; +;; Author: Artur Malabarba +;; Bozhidar Batsov +;; Vitalie Spinu +;; +;; Keywords: languages, clojure, cider +;; +;; 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 . +;; +;; This file is not part of GNU Emacs. +;; +;;; Code: + +(require 'nrepl-client) +(require 'sesman) + +(defcustom cider-connection-message-fn #'cider-random-words-of-inspiration + "The function to use to generate the message displayed on connect. +When set to nil no additional message will be displayed. A good +alternative to the default is `cider-random-tip'." + :type 'function + :group 'cider + :package-version '(cider . "0.11.0")) + + +;;; Connection Management + +(defun cider-connected-p () + "Return t if CIDER is currently connected, nil otherwise." + (not (null (sesman-sessions 'CIDER)))) + +(defun cider-ensure-connected () + "Ensure there is a cider session present." + (sesman-ensure-session "Cider session: " 'ask-new)) + +(defun cider--connect (params) + (process-buffer + (nrepl-start-client-process + (plist-get params :host) + (plist-get params :port) + (plist-get params :server) + (lambda (_) + (cider-repl-create params))))) + +(defun cider--gather-connect-params (repl-or-server-buffer) + (with-current-buffer repl-or-server-buffer + (unless nrepl-endpoint + (error "This is not a REPL or SERVER buffer; is there an active REPL?")) + (let ((server-buf (if (nrepl-server-p repl-or-server-buffer) + repl-or-server-buffer + nrepl-server-buffer))) + (append nrepl-endpoint + (list :project-dir nrepl-project-dir) + (when (buffer-live-p server-buf) + (list + :server (get-buffer-process server-buf) + :server-command nrepl-server-command)) + ;; repl-specific parameters (do not pollute server params!) + (unless (nrepl-server-p repl-or-server-buffer) + (list :repl-type cider-repl-type + :repl-init-function cider-repl-init-function)))))) + +(defun cider--close-buffer (buffer) + "Close the BUFFER and kill its associated process (if any)." + (when (buffer-live-p buffer) + (when-let* ((proc (get-buffer-process buffer))) + (when (process-live-p proc) + (delete-process proc))) + (kill-buffer buffer))) + +(defun cider--close-connection (connection &optional no-kill) + "Close CONNECTION. +Also close associated REPL buffer. When NO-KILL is non-nil stop the +connection but don't kill the REPL buffer." + (when (buffer-live-p connection) + (with-current-buffer connection + (when spinner-current (spinner-stop)) + (when nrepl-tunnel-buffer + (cider--close-buffer nrepl-tunnel-buffer))) + (let ((proc (get-buffer-process connection))) + (when (and (process-live-p proc) + (or (not nrepl-server-buffer) + ;; Sync request will hang if the server is dead. + (process-live-p (get-buffer-process nrepl-server-buffer)))) + (nrepl-sync-request:close connection) + (delete-process proc))) + (sesman-remove-object 'CIDER nil connection t t) + (when-let* ((messages-buffer (and nrepl-log-messages + (nrepl-messages-buffer connection)))) + (kill-buffer messages-buffer)) + (if no-kill + (with-current-buffer connection + (goto-char (point-max)) + (insert (format "\n;; Closed on %s\n" (current-time-string)))) + (kill-buffer connection)))) + +(defun cider--connected-handler () + "Handle CIDER initialization after nREPL connection has been established. +This function is appended to `nrepl-connected-hook' in the client process +buffer." + ;; `nrepl-connected-hook' is run in the connection buffer + ;; `cider-enlighten-mode' changes eval to include the debugger, so we inhibit + ;; it here as the debugger isn't necessarily initialized yet + (let ((cider-enlighten-mode nil)) + ;; after initialization, set mode-line and buffer name. + (cider-repl-set-type cider-repl-type) + (cider-repl-init (current-buffer)) + (cider--check-required-nrepl-version) + (cider--check-clojure-version-supported) + (cider--check-middleware-compatibility) + (when cider-redirect-server-output-to-repl + (cider--subscribe-repl-to-server-out)) + (when cider-auto-mode + (cider-enable-on-existing-clojure-buffers)) + ;; Middleware on cider-nrepl's side is deferred until first usage, but + ;; loading middleware concurrently can lead to occasional "require" issues + ;; (likely a Clojure bug). Thus, we load the heavy debug middleware towards + ;; the end, allowing for the faster "server-out" middleware to load + ;; first. + (cider--debug-init-connection) + (when cider-repl-init-function + (funcall cider-repl-init-function)) + (run-hooks 'cider-connected-hook))) + +(defun cider--disconnected-handler () + "Cleanup after nREPL connection has been lost or closed. +This function is appended to `nrepl-disconnected-hook' in the client +process buffer." + ;; `nrepl-connected-hook' is run in the connection buffer + (cider-possibly-disable-on-existing-clojure-buffers) + (sesman-remove-object 'CIDER nil (current-buffer) t t) + (run-hooks 'cider-disconnected-hook)) + + +;;; Cider's connection-wise management + +(defun cider-close-ancillary-buffers () + "Close buffers that are shared across connections." + (interactive) + (dolist (buf-name cider-ancillary-buffers) + (when (get-buffer buf-name) + (kill-buffer buf-name)))) + +(defun cider-quit () + "Quit the currently active CIDER connection." + (interactive) + (cider-ensure-connected) + (let ((connection (cider-current-connection))) + (cider--close-connection connection)) + ;; if there are no more connections we can kill all ancillary buffers + (unless (cider-connected-p) + (cider-close-ancillary-buffers))) + +(defun cider-restart () + "Restart the currently active CIDER connection. +Don't restart the server or other connections within the same session. Use +`sesman-restart' to restart the entire session." + (interactive) + (let* ((repl (cider-current-connection)) + (params (thread-first (cider--gather-connect-params repl) + (plist-put :session-name (sesman-get-session-name-for-object 'CIDER repl)) + (plist-put :repl-buffer repl)))) + (cider--close-connection repl 'no-kill) + (cider--connect params))) + +(defun cider-describe-current-connection () + "Display information about the current connection." + (interactive) + (message "%s" (cider--connection-info (cider-current-connection)))) +(define-obsolete-function-alias 'cider-display-connection-info 'cider-describe-current-connection "0.18.0") + +(defun cider-describe-nrepl-session () + "Describe an nREPL session." + (interactive) + (cider-ensure-connected) + (let* ((repl (cider-current-connection)) + (selected-session (completing-read "Describe nREPL session: " (nrepl-sessions repl)))) + (when (and selected-session (not (equal selected-session ""))) + (let* ((session-info (nrepl-sync-request:describe repl)) + (ops (nrepl-dict-keys (nrepl-dict-get session-info "ops"))) + (session-id (nrepl-dict-get session-info "session")) + (session-type (cond + ((equal session-id (cider-nrepl-eval-session)) "Active eval") + ((equal session-id (cider-nrepl-tooling-session)) "Active tooling") + (t "Unknown")))) + (with-current-buffer (cider-popup-buffer cider-nrepl-session-buffer) + (read-only-mode -1) + (insert (format "Session: %s\n" session-id) + (format "Type: %s session\n" session-type) + (format "Supported ops:\n")) + (mapc (lambda (op) (insert (format " * %s\n" op))) ops))) + (display-buffer cider-nrepl-session-buffer)))) + + +;;; Sesman's session-wise management + +(cl-defmethod sesman-session-info ((system (eql CIDER)) session) + (interactive "P") + (let ((infos (mapcar (lambda (repl) + (format "\t%s / buffer %s" + (cider--connection-info repl) + (buffer-name repl))) + (cdr session)))) + (if infos + (mapconcat #'identity infos "\n") + (message "No %s sessions" system)))) + +(declare-function cider-jack-in-cljcljs "cider") +(cl-defmethod sesman-start-session ((system (eql CIDER))) + "Start a clj session with a cljs REPL if cljs requirements are met." + (cider-jack-in-cljcljs nil t)) + +(cl-defmethod sesman-quit-session ((system (eql CIDER)) session) + (mapc #'cider--close-connection (cdr session)) + ;; if there are no more connections we can kill all ancillary buffers + (unless (cider-connected-p) + (cider-close-ancillary-buffers))) + +(cl-defmethod sesman-restart-session ((system (eql CIDER)) session) + (let* ((repls (cdr session)) + (s-buf (seq-some (lambda (r) + (buffer-local-value 'nrepl-server-buffer r)) + repls)) + (s-params (cider--gather-connect-params s-buf)) + (ses-name (car session))) + ;; 1) kill all connections, but keep the buffers + (mapc (lambda (conn) + (cider--close-connection conn 'no-kill)) + repls) + ;; 2) kill the server + ;; Workaround for a nasty race condition https://github.com/clojure-emacs/cider/issues/439 + ;; TODO: Find a better way to ensure `cider-quit' has finished + (message "Waiting for CIDER server to quit...") + (nrepl-kill-server-buffer s-buf) + (sleep-for 2) + ;; 3) start server + (nrepl-start-server-process + (plist-get s-params :project-dir) + (plist-get s-params :server-command) + (lambda (server-buf) + ;; 4) restart the repls reusing the buffer + (dolist (r repls) + (cider--connect + ;; server params (:port, :project-dir etc) have precedence + (thread-first (append (cider--gather-connect-params server-buf) + (cider--gather-connect-params r)) + (plist-put :session-name ses-name) + (plist-put :repl-buffer r)))) + (message "Restarted CIDER %s session" ses-name))))) + +(defun cider-new-session-name (params) + (let* ((dir (or (plist-get params :project-dir) + (clojure-project-dir (cider-current-dir)) + default-directory)) + (host (plist-get params :host)) + ;; showing host:port on remotes only + (host-port (if (not (or (null host) + (equal host "localhost") + (equal host "127.0.0.1"))) + (format ":%s:%s" host (plist-get params :port)) + "")) + (port (plist-get params :port)) + (root-name (file-name-nondirectory (directory-file-name dir))) + (name (format "%s%s" root-name host-port)) + (other-names (mapcar #'car (sesman-sessions 'CIDER))) + (i 2)) + (while (member name other-names) + (setq name (concat root-name "#" (number-to-string i)) + i (+ i 1))) + name)) + +(defun cider--install-sesman () + (setq sesman-system 'CIDER + sesman-session-object-type 'buffer)) +(add-hook 'clojure-mode-hook 'cider--install-sesman) + + +;;; Current/other REPLs + +(defun cider-connection-type-for-buffer (&optional buffer) + "Return the matching connection type (clj or cljs) for BUFFER. +In cljc buffers return \"multi\". This function infers connection type +based on the major mode. BUFFER defaults to the `current-buffer'." + (with-current-buffer (or buffer (current-buffer)) + (cond + ((derived-mode-p 'clojurescript-mode) "cljs") + ((derived-mode-p 'clojurec-mode) "multi") + ((derived-mode-p 'clojure-mode) "clj") + (cider-repl-type)))) + +(defun cider-connections (&optional type) + "Return cider repls of TYPE from current session. +If TYPE is nil, return all repls." + (let ((repls (cdr (sesman-current-session 'CIDER)))) + (if (or (null type) (string= type "multi")) + repls + (seq-filter (lambda (b) + (string= type (cider--connection-type b))) + repls)))) + +(defun cider-current-connection (&optional type) + "Get first repl of TYPE from current session. +TYPE is either \"clj\" or \"cljs\". When nil, infer the REPL from the +current buffer." + (if (and (derived-mode-p 'cider-repl-mode) + (or (null type) + (string= cider-repl-type type))) + ;; shortcut when in REPL buffer + (current-buffer) + (let ((type (or type (cider-connection-type-for-buffer)))) + (car (cider-connections type))))) + +(defun cider-other-repl (&optional repl) + "Return the first repl buffer of another type than REPL. +REPL defaults to `cider-current-connection'." + (when-let* ((repl (or repl (cider-current-connection))) + (type (cider--connection-type repl))) + (cider-current-connection (if (equal type "clj") "cljs" "clj")))) + + + +;;; Connection info + +(defun cider--java-version () + "Retrieve the underlying connection's Java version." + (with-current-buffer (cider-current-connection) + (when nrepl-versions + (thread-first nrepl-versions + (nrepl-dict-get "java") + (nrepl-dict-get "version-string"))))) + +(defun cider--clojure-version () + "Retrieve the underlying connection's Clojure version." + (with-current-buffer (cider-current-connection) + (when nrepl-versions + (thread-first nrepl-versions + (nrepl-dict-get "clojure") + (nrepl-dict-get "version-string"))))) + +(defun cider--nrepl-version () + "Retrieve the underlying connection's nREPL version." + (with-current-buffer (cider-current-connection) + (when nrepl-versions + (thread-first nrepl-versions + (nrepl-dict-get "nrepl") + (nrepl-dict-get "version-string"))))) + +(defun cider--connection-info (connection-buffer) + "Return info about CONNECTION-BUFFER. +Info contains project name, current REPL namespace, host:port +endpoint and Clojure version." + (with-current-buffer connection-buffer + (format "%s%s@%s:%s (Java %s, Clojure %s, nREPL %s)" + (upcase (concat cider-repl-type " ")) + (or (cider--project-name nrepl-project-dir) "") + (plist-get nrepl-endpoint :host) + (plist-get nrepl-endpoint :port) + (cider--java-version) + (cider--clojure-version) + (cider--nrepl-version)))) + +(defun cider--connection-properties (conn-buffer) + "Extract the essential properties of CONN-BUFFER." + (with-current-buffer conn-buffer + (list + :type cider-repl-type + :host (car nrepl-endpoint) + :port (cadr nrepl-endpoint) + :project-dir nrepl-project-dir))) + +(defun cider--connection-type (conn-buffer) + "Get CONN-BUFFER's type. +Return value matches `cider-repl-type'." + (plist-get (cider--connection-properties conn-buffer) :type)) + +(defun cider--connection-host (conn-buffer) + "Get CONN-BUFFER's host." + (plist-get (cider--connection-properties conn-buffer) :host)) + +(defun cider--connection-port (conn-buffer) + "Get CONN-BUFFER's port." + (plist-get (cider--connection-properties conn-buffer) :port)) + +(defun cider--connection-project-dir (conn-buffer) + "Get CONN-BUFFER's project dir." + (plist-get (cider--connection-properties conn-buffer) :project-dir)) + +(provide 'cider-connection) diff --git a/cider-interaction.el b/cider-interaction.el index e0f3f1afb..135ef8dc0 100644 --- a/cider-interaction.el +++ b/cider-interaction.el @@ -75,7 +75,6 @@ navigate to this buffer." (defcustom cider-auto-jump-to-error t "Control the cursor jump behaviour in compilation error buffer. - When non-nil automatically jump to error location during interactive compilation. When set to 'errors-only, don't jump to warnings. When set to nil, don't jump at all." @@ -92,7 +91,6 @@ When set to nil, don't jump at all." (defcustom cider-auto-track-ns-form-changes t "Controls whether to auto-evaluate a source buffer's ns form when changed. - When non-nil CIDER will check for ns form changes before each eval command. When nil the users are expected to take care of the re-evaluating updated ns forms manually themselves." @@ -139,7 +137,6 @@ If t, save the files without confirmation." (defcustom cider-annotate-completion-function #'cider-default-annotate-completion-function "Controls how the annotations for completion candidates are formatted. - Must be a function that takes two arguments: the abbreviation of the candidate type according to `cider-completion-annotations-alist' and the candidate's namespace." @@ -190,11 +187,8 @@ if the candidate is not namespace-qualified." (defcustom cider-refresh-show-log-buffer nil "Controls when to display the refresh log buffer. - If non-nil, the log buffer will be displayed every time `cider-refresh' is -called. - -If nil, the log buffer will still be written to, but will never be +called. If nil, the log buffer will still be written to, but will never be displayed automatically. Instead, the most relevant information will be displayed in the echo area." :type '(choice (const :tag "always" t) @@ -204,7 +198,6 @@ displayed in the echo area." (defcustom cider-refresh-before-fn nil "Clojure function for `cider-refresh' to call before reloading. - If nil, nothing will be invoked before reloading. Must be a namespace-qualified function of zero arity. Any thrown exception will prevent reloading from occurring." @@ -214,7 +207,6 @@ prevent reloading from occurring." (defcustom cider-refresh-after-fn nil "Clojure function for `cider-refresh' to call after reloading. - If nil, nothing will be invoked after reloading. Must be a namespace-qualified function of zero arity." :type 'string @@ -293,7 +285,6 @@ Its value can be either 'jack-in or 'connect.") (defun cider-read-from-minibuffer (prompt &optional value) "Read a string from the minibuffer, prompting with PROMPT. If VALUE is non-nil, it is inserted into the minibuffer as initial-input. - PROMPT need not end with \": \". If it doesn't, VALUE is displayed on the prompt as a default value (used if the user doesn't type anything) and is not used as initial input (input is left empty)." @@ -326,7 +317,6 @@ not used as initial input (input is left empty)." (defun cider-clear-compilation-highlights (&optional arg) "Remove compilation highlights. - When invoked with a prefix ARG the command doesn't prompt for confirmation." (interactive "P") (when (or arg (y-or-n-p "Are you sure you want to clear the compilation highlights? ")) @@ -393,21 +383,19 @@ If OTHER-WINDOW is non-nil don't reuse current window." (defun cider-find-dwim (symbol-file) "Find and display the SYMBOL-FILE at point. - -SYMBOL-FILE could be a var or a resource. If thing at point is empty -then show dired on project. If var is not found, try to jump to resource -of the same name. When called interactively, a prompt is given according -to the variable `cider-prompt-for-symbol'. A single or double prefix argument -inverts the meaning. A prefix of `-' or a double prefix argument causes the -results to be displayed in a different window. -A default value of thing at point is given when prompted." +SYMBOL-FILE could be a var or a resource. If thing at point is empty then +show dired on project. If var is not found, try to jump to resource of the +same name. When called interactively, a prompt is given according to the +variable `cider-prompt-for-symbol'. A single or double prefix argument +inverts the meaning. A prefix of `-' or a double prefix argument causes +the results to be displayed in a different window. A default value of thing +at point is given when prompted." (interactive (cider--find-dwim-interactive "Jump to: ")) (cider--find-dwim symbol-file `cider-find-dwim (cider--open-other-window-p current-prefix-arg))) (defun cider--find-dwim (symbol-file callback &optional other-window) "Find the SYMBOL-FILE at point. - CALLBACK upon failure to invoke prompt if not prompted previously. Show results in a different window if OTHER-WINDOW is true." (if-let* ((info (cider-var-info symbol-file))) @@ -431,7 +419,6 @@ Show results in a different window if OTHER-WINDOW is true." (defun cider-find-resource (path) "Find the resource at PATH. - Prompt for input as indicated by the variable `cider-prompt-for-symbol'. A single or double prefix argument inverts the meaning of `cider-prompt-for-symbol'. A prefix argument of `-` or a double prefix @@ -458,7 +445,6 @@ value is thing at point." (defun cider--invert-prefix-arg (arg) "Invert the effect of prefix value ARG on `cider-prompt-for-symbol'. - This function preserves the `other-window' meaning of ARG." (let ((narg (prefix-numeric-value arg))) (pcase narg @@ -477,7 +463,6 @@ This function preserves the `other-window' meaning of ARG." (defun cider--prompt-for-symbol-p (&optional prefix) "Check if cider should prompt for symbol. - Tests againsts PREFIX and the value of `cider-prompt-for-symbol'. Invert meaning of `cider-prompt-for-symbol' if PREFIX indicates it should be." (if (cider--prefix-invert-prompt-p prefix) @@ -492,7 +477,6 @@ Optionally open it in a different window if OTHER-WINDOW is truthy." (defun cider-find-ns (&optional arg ns) "Find the file containing NS. - A prefix ARG of `-` or a double prefix argument causes the results to be displayed in a different window." (interactive "P") @@ -618,16 +602,12 @@ Put type and ns properties on the candidate" (defun cider-annotate-symbol (symbol) "Return a string suitable for annotating SYMBOL. - If SYMBOL has a text property `type` whose value is recognised, its abbreviation according to `cider-completion-annotations-alist' will be used. If `type` is present but not recognised, its value will be used -unaltered. - -If SYMBOL has a text property `ns`, then its value will be used according -to `cider-completion-annotations-include-ns'. - -The formatting is performed by `cider-annotate-completion-function'." +unaltered. If SYMBOL has a text property `ns`, then its value will be used +according to `cider-completion-annotations-include-ns'. The formatting is +performed by `cider-annotate-completion-function'." (when cider-annotate-completion-candidates (let* ((type (cider-completion--get-candidate-type symbol)) (ns (cider-completion--get-candidate-ns symbol))) @@ -647,7 +627,6 @@ The formatting is performed by `cider-annotate-completion-function'." (defun cider-completion-flush-caches () "Force Compliment to refill its caches. - This command should be used if Compliment fails to pick up new classnames and methods from dependencies that were loaded dynamically after the REPL has started." @@ -656,7 +635,6 @@ has started." (defun cider-company-location (var) "Open VAR's definition in a buffer. - Returns the cons of the buffer itself and the location of VAR's definition in the buffer." (when-let* ((info (cider-var-info var)) @@ -726,7 +704,6 @@ The handler simply inserts the result value in BUFFER." (defun cider--emit-interactive-eval-output (output repl-emit-function) "Emit output resulting from interactive code evaluation. - The OUTPUT can be sent to either a dedicated output buffer or the current REPL buffer. This is controlled by `cider-interactive-eval-output-destination'. REPL-EMIT-FUNCTION emits the OUTPUT." @@ -741,7 +718,6 @@ REPL-EMIT-FUNCTION emits the OUTPUT." (defun cider-emit-interactive-eval-output (output) "Emit OUTPUT resulting from interactive code evaluation. - The output can be send to either a dedicated output buffer or the current REPL buffer. This is controlled via `cider-interactive-eval-output-destination'." @@ -749,7 +725,6 @@ REPL buffer. This is controlled via (defun cider-emit-interactive-eval-err-output (output) "Emit err OUTPUT resulting from interactive code evaluation. - The output can be send to either a dedicated output buffer or the current REPL buffer. This is controlled via `cider-interactive-eval-output-destination'." @@ -832,9 +807,8 @@ or it can be a list with (START END) of the evaluated region." (defun cider-eval-print-with-comment-handler (buffer location comment-prefix) "Make a handler for evaluating and printing commented results in BUFFER. - -LOCATION is the location at which to insert. -COMMENT-PREFIX is the comment prefix to use." +LOCATION is the location at which to insert. COMMENT-PREFIX is the comment +prefix to use." (nrepl-make-response-handler buffer (lambda (buffer value) (with-current-buffer buffer @@ -874,7 +848,6 @@ COMMENT-POSTFIX is the text to output after the last line." (defun cider-popup-eval-out-handler (&optional buffer) "Make a handler for evaluating and printing stdout/stderr in popup BUFFER. - This is used by pretty-printing commands and intentionally discards their results." (cl-flet ((popup-output-handler (buffer str) (cider-emit-into-popup-buffer buffer @@ -926,7 +899,6 @@ They exist for compatibility with `next-error'." (defun cider--show-error-buffer-p () "Return non-nil if the error buffer must be shown on error. - Takes into account both the value of `cider-show-error-buffer' and the currently selected buffer." (let* ((selected-buffer (window-buffer (selected-window))) @@ -978,7 +950,6 @@ op/situation that originated this error." (defun cider--handle-stacktrace-response (response causes) "Handle stacktrace op RESPONSE, aggregating the result into CAUSES. - If RESPONSE contains a cause, cons it onto CAUSES and return that. If RESPONSE is the final message (i.e. it contains a status), render CAUSES into a new error buffer." @@ -1049,7 +1020,6 @@ See `compilation-error-regexp-alist' for help on their format.") (defun cider--goto-expression-start () "Go to the beginning a list, vector, map or set outside of a string. - We do so by starting and the current position and proceeding backwards until we find a delimiters that's not inside a string." (if (and (looking-back "[])}]" (line-beginning-position)) @@ -1229,7 +1199,6 @@ If invoked with OUTPUT-TO-CURRENT-BUFFER, print the result in the current buffer (defun cider-eval-sexp-at-point (&optional output-to-current-buffer) "Evaluate the expression around point. - If invoked with OUTPUT-TO-CURRENT-BUFFER, output the result to current buffer." (interactive "P") (save-excursion @@ -1238,7 +1207,6 @@ If invoked with OUTPUT-TO-CURRENT-BUFFER, output the result to current buffer." (defvar-local cider-previous-eval-context nil "The previous evaluation context if any. - That's set by commands like `cider-eval-last-sexp-in-context'.") (defun cider--eval-in-context (code) @@ -1253,7 +1221,6 @@ That's set by commands like `cider-eval-last-sexp-in-context'.") (defun cider-eval-last-sexp-in-context () "Evaluate the preceding sexp in user-supplied context. - The context is just a let binding vector (without the brackets). The context is remembered between command invocations." (interactive) @@ -1439,7 +1406,6 @@ command `cider-debug-defun-at-point'." (defun cider--calculate-opening-delimiters () "Walks up the list of expressions to collect all sexp opening delimiters. - The result is a list of the delimiters. That function is used in `cider-eval-defun-to-point' so it can make an @@ -1469,7 +1435,6 @@ incomplete expression complete." (defun cider-eval-defun-to-point () "Evaluate the current toplevel form up to point. - It constructs an expression to eval in the following manner: - It find the code between the point and the start of the toplevel expression; @@ -1640,7 +1605,6 @@ See command `cider-mode'." (defun cider-toggle-trace-var (arg) "Toggle var tracing. - Prompts for the symbol to use, or uses the symbol at point, depending on the value of `cider-prompt-for-symbol'. With prefix arg ARG, does the opposite of what that option dictates." @@ -1993,118 +1957,6 @@ START and END represent the region's boundaries." "Create an interrupt response handler for BUFFER." (nrepl-make-response-handler buffer nil nil nil nil)) -(defun cider-describe-nrepl-session () - "Describe an nREPL session." - (interactive) - (cider-ensure-connected) - (let ((selected-session (completing-read "Describe nREPL session: " (nrepl-sessions (cider-current-connection))))) - (when (and selected-session (not (equal selected-session ""))) - (let* ((session-info (nrepl-sync-request:describe (cider-current-connection))) - (ops (nrepl-dict-keys (nrepl-dict-get session-info "ops"))) - (session-id (nrepl-dict-get session-info "session")) - (session-type (cond - ((equal session-id (cider-current-session)) "Active eval") - ((equal session-id (cider-current-tooling-session)) "Active tooling") - (t "Unknown")))) - (with-current-buffer (cider-popup-buffer cider-nrepl-session-buffer) - (read-only-mode -1) - (insert (format "Session: %s\n" session-id) - (format "Type: %s session\n" session-type) - (format "Supported ops:\n")) - (mapc (lambda (op) (insert (format " * %s\n" op))) ops))) - (display-buffer cider-nrepl-session-buffer)))) - -(defun cider-close-nrepl-session () - "Close an nREPL session for the current connection." - (interactive) - (cider-ensure-connected) - (nrepl-sync-request:close (cider-current-connection)) - (message "Closed nREPL session")) - -;;; quiting -(defun cider--close-buffer (buffer) - "Close the BUFFER and kill its associated process (if any)." - (when (buffer-live-p buffer) - (with-current-buffer buffer - (when-let* ((proc (get-buffer-process buffer))) - (when (process-live-p proc) - (when (or (not nrepl-server-buffer) - ;; Sync request will hang if the server is dead. - (process-live-p (get-buffer-process nrepl-server-buffer))) - (when (or nrepl-session nrepl-tooling-session) - (nrepl-sync-request:close buffer))) - (when proc (delete-process proc))))) - (kill-buffer buffer))) - -(defun cider-close-ancillary-buffers () - "Close buffers that are shared across connections." - (interactive) - (dolist (buf-name cider-ancillary-buffers) - (when (get-buffer buf-name) - (kill-buffer buf-name)))) - -(defun cider--quit-connection (conn) - "Quit the connection CONN." - (when conn - (cider--close-connection-buffer conn))) - -(defvar cider-scratch-buffer-name) -(defun cider-quit (&optional quit-all) - "Quit the currently active CIDER connection. -With a prefix argument QUIT-ALL the command will kill all connections -and all ancillary CIDER buffers." - (interactive "P") - (cider-ensure-connected) - (if (and quit-all (y-or-n-p "Are you sure you want to quit all CIDER connections? ")) - (progn - (when-let* ((scratch (get-buffer cider-scratch-buffer-name))) - (when (y-or-n-p (format "Kill %s buffer? " cider-scratch-buffer-name)) - (kill-buffer cider-scratch-buffer-name))) - (dolist (connection cider-connections) - (cider--quit-connection connection)) - (message "All active nREPL connections were closed")) - (let ((connection (cider-current-connection))) - (when (y-or-n-p (format "Are you sure you want to quit the current CIDER connection %s? " - (cider-propertize (buffer-name connection) 'bold))) - (cider--quit-connection connection)))) - ;; if there are no more connections we can kill all ancillary buffers - (unless (cider-connected-p) - (cider-close-ancillary-buffers))) - -(declare-function cider-connect "cider") -(declare-function cider-jack-in "cider") -(defun cider--restart-connection (conn) - "Restart the connection CONN." - (let ((project-dir (with-current-buffer conn nrepl-project-dir)) - (buf-name (buffer-name conn)) - ;; save these variables before we kill the connection - (conn-creation-method (with-current-buffer conn cider-connection-created-with)) - (conn-endpoint (with-current-buffer conn nrepl-endpoint))) - (cider--quit-connection conn) - ;; Workaround for a nasty race condition https://github.com/clojure-emacs/cider/issues/439 - ;; TODO: Find a better way to ensure `cider-quit' has finished - (message "Waiting for CIDER connection %s to quit..." - (cider-propertize buf-name 'bold)) - (sleep-for 2) - (pcase conn-creation-method - (`connect (apply #'cider-connect conn-endpoint)) - (`jack-in (if project-dir - (let ((default-directory project-dir)) - (cider-jack-in)) - (error "Can't restart CIDER connection for unknown project"))) - (_ (error "Unexpected value %S for `cider-connection-created-with'" - conn-creation-method))))) - -(defun cider-restart (&optional restart-all) - "Restart the currently active CIDER connection. -If RESTART-ALL is t, then restarts all connections." - (interactive "P") - (cider-ensure-connected) - (if restart-all - (dolist (conn cider-connections) - (cider--restart-connection conn)) - (cider--restart-connection (cider-current-connection)))) - (defvar cider--namespace-history nil "History of user input for namespace prompts.") diff --git a/cider-mode.el b/cider-mode.el index 5c7407eb0..94805a952 100644 --- a/cider-mode.el +++ b/cider-mode.el @@ -49,7 +49,6 @@ (defun cider--modeline-info () "Return info for the cider mode modeline. - Info contains the connection type, project name and host:port endpoint." (if-let* ((current-connection (ignore-errors (cider-current-connection)))) (with-current-buffer current-connection @@ -178,7 +177,7 @@ See also the related commands `cider-repl-clear-buffer' and `cider-repl-clear-output'." (interactive "P") (let ((origin-buffer (current-buffer))) - (switch-to-buffer (cider-current-repl-buffer)) + (switch-to-buffer (cider-current-connection)) (if clear-repl (cider-repl-clear-buffer) (cider-repl-clear-output)) @@ -194,20 +193,20 @@ See also the related commands `cider-repl-clear-buffer' and :help "Connects to a REPL that's already running."] ["Replicate connection" cider-replicate-connection :help "Opens another connection based on a existing one. The new connection uses the same host and port as the base connection."] - ["Quit" cider-quit :active cider-connections] - ["Restart" cider-restart :active cider-connections] + ["Quit" cider-quit :active (cider-connections)] + ["Restart" cider-restart :active (cider-connections)] ("ClojureScript" ["Start a Clojure REPL, and a ClojureScript REPL" cider-jack-in-clojurescript :help "Starts an nREPL server, connects a Clojure REPL to it, and then a ClojureScript REPL. Configure `cider-cljs-repl-types' to change the ClojureScript REPL to use for your build tool."] ["Connect to a ClojureScript REPL" cider-connect-clojurescript :help "Connects to a ClojureScript REPL that's already running."] - ["Create a ClojureScript REPL from a Clojure REPL" cider-create-sibling-cljs-repl]) + ["Create a ClojureScript REPL from a Clojure REPL" cider-jack-in-sibling-clojurescript]) "--" - ["Connection info" cider-display-connection-info - :active cider-connections] + ["Connection info" cider-describe-current-connection + :active (cider-connections)] ["Rotate default connection" cider-rotate-default-connection - :active (cdr cider-connections)] + :active (cdr (cider-connections))] ["Select any CIDER buffer" cider-selector] "--" ["Configure CIDER" (customize-group 'cider)] @@ -220,14 +219,13 @@ Configure `cider-cljs-repl-types' to change the ClojureScript REPL to use for yo "--" ["Close ancillary buffers" cider-close-ancillary-buffers :active (seq-remove #'null cider-ancillary-buffers)] - ("nREPL" :active cider-connections - ["Describe session" cider-describe-nrepl-session] - ["Close session" cider-close-nrepl-session] + ("nREPL" :active (cider-connections) + ["Describe nrepl session" cider-describe-nrepl-session] ["Toggle message logging" nrepl-toggle-message-logging])) "Menu for CIDER mode.") (defconst cider-mode-eval-menu - '("CIDER Eval" :visible cider-connections + '("CIDER Eval" :visible (cider-connections) ["Eval top-level sexp" cider-eval-defun-at-point] ["Eval top-level sexp to point" cider-eval-defun-to-point] ["Eval current sexp" cider-eval-sexp-at-point] @@ -262,7 +260,7 @@ Configure `cider-cljs-repl-types' to change the ClojureScript REPL to use for yo "Menu for CIDER mode eval commands.") (defconst cider-mode-interactions-menu - `("CIDER Interactions" :visible cider-connections + `("CIDER Interactions" :visible (cider-connections) ["Complete symbol" complete-symbol] "--" ("REPL" @@ -355,7 +353,7 @@ Configure `cider-cljs-repl-types' to change the ClojureScript REPL to use for yo (define-key map (kbd "C-c C-t") 'cider-test-commands-map) (define-key map (kbd "C-c M-s") #'cider-selector) (define-key map (kbd "C-c M-r") #'cider-rotate-default-connection) - (define-key map (kbd "C-c M-d") #'cider-display-connection-info) + (define-key map (kbd "C-c M-d") #'cider-describe-current-connection) (define-key map (kbd "C-c C-=") #'cider-profile-map) (define-key map (kbd "C-c C-x") #'cider-refresh) (define-key map (kbd "C-c C-q") #'cider-quit) @@ -509,7 +507,7 @@ Search is done with the given LIMIT." (defun cider--anchored-search-suppressed-forms-internal (limit) "Helper function for `cider--anchored-search-suppressed-forms`. LIMIT is the same as the LIMIT in `cider--anchored-search-suppressed-forms`" - (let ((types (cider-project-connections-types))) + (let ((types (seq-uniq (seq-map #'cider--connection-type (cider-connections))))) (when (= (length types) 1) (let ((type (car types)) (expr (read (current-buffer))) diff --git a/cider-repl.el b/cider-repl.el index 61ced97ae..4b77d787c 100644 --- a/cider-repl.el +++ b/cider-repl.el @@ -56,6 +56,9 @@ :prefix "cider-repl-" :group 'cider) +(defvar-local cider-repl-type nil + "The type of this REPL buffer, usually either \"clj\" or \"cljs\".") + (defface cider-repl-prompt-face '((t (:inherit font-lock-keyword-face))) "Face for the prompt in the REPL buffer." @@ -222,8 +225,7 @@ Currently its only purpose is to facilitate `cider-repl-clear-buffer'.") (defvar-local cider-repl-ns-cache nil "A dict holding information about all currently loaded namespaces. -This cache is stored in the connection buffer. Other buffer's access it -via `cider-current-connection'.") +This cache is stored in the connection buffer.") (defvar cider-mode) (declare-function cider-refresh-dynamic-font-lock "cider-mode") @@ -250,28 +252,31 @@ via `cider-current-connection'.") (cider-refresh-dynamic-font-lock ns-dict)))))))))) (declare-function cider-default-err-handler "cider-interaction") - -(defun cider-repl-create (endpoint) - "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* ((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) +(defvar-local cider-repl-init-function nil) +(defun cider-repl-create (params) + "Create new repl buffer. +PARAMS is a plist which contains :repl-type, :host, :port, :project-dir, +:repl-init-function and :session-name. When non-nil, :repl-init-function must be a +function with no arguments which is called after repl creation function +with the repl buffer set as current." + ;; Connection might not have been set as yet. Please don't send requests in + ;; this function, but use cider--connected-handler instead. + (let ((buffer (or (plist-get params :repl-buffer) + (get-buffer-create (generate-new-buffer-name "*cider-uninitialized-repl*"))))) + (with-current-buffer buffer + ;; register with sesman session first + (let ((ses-name (or (plist-get params :session-name) + (cider-new-session-name params)))) + (sesman-add-object 'CIDER ses-name buffer t)) (unless (derived-mode-p 'cider-repl-mode) - (cider-repl-mode) - (cider-repl-set-type "clj")) - (setq nrepl-err-handler #'cider-default-err-handler) + (cider-repl-mode)) + (setq nrepl-err-handler #'cider-default-err-handler + ;; used as a new-repl marker in cider-repl-set-type + mode-name nil + ;; REPLs start with clj and then "upgrade" to a different type + cider-repl-type "clj" + ;; ran at the end of cider--connected-handler + cider-repl-init-function (plist-get params :repl-init-function)) (cider-repl-reset-markers) (add-hook 'nrepl-response-handler-functions #'cider-repl--state-handler nil 'local) (add-hook 'nrepl-connected-hook 'cider--connected-handler nil 'local) @@ -695,7 +700,7 @@ If BOL is non-nil insert at the beginning of line. Run (defun cider-repl--emit-interactive-output (string face) "Emit STRING as interactive output using FACE." - (with-current-buffer (cider-current-repl-buffer) + (with-current-buffer (cider-current-connection) (let ((pos (cider-repl--end-of-line-before-input-start)) (string (replace-regexp-in-string "\n\\'" "" string))) (cider-repl--emit-output-at-pos (current-buffer) string face pos t)))) @@ -1160,12 +1165,9 @@ With a prefix argument CLEAR-REPL it will clear the entire REPL buffer instead." (defun cider-repl-set-ns (ns) "Switch the namespace of the REPL buffer to NS. - -If called from a cljc buffer act on both the Clojure and -ClojureScript REPL if there are more than one REPL present. - -If invoked in a REPL buffer the command will prompt for the name of the -namespace to switch to." +If called from a cljc buffer act on both the Clojure and ClojureScript REPL +if there are more than one REPL present. If invoked in a REPL buffer the +command will prompt for the name of the namespace to switch to." (interactive (list (if (or (derived-mode-p 'cider-repl-mode) (null (cider-ns-form))) (completing-read "Switch to namespace: " @@ -1180,16 +1182,17 @@ namespace to switch to." :both)) (defun cider-repl-set-type (&optional type) - "Set REPL TYPE to \"clj\" or \"cljs\"." - (interactive) + "Set REPL TYPE to \"clj\" or \"cljs\". +Assume that the current buffer is a REPL." (let ((type (or type (completing-read (format "Set REPL type (currently `%s') to: " cider-repl-type) '("clj" "cljs"))))) - (setq cider-repl-type type) - (if (equal type "cljs") - (setq mode-name "REPL[cljs]") - (setq mode-name "REPL[clj]")))) + (when (or (not (equal cider-repl-type type)) + (null mode-name)) + (setq cider-repl-type type) + (setq mode-name (format "REPL[%s]" type)) + (rename-buffer (nrepl-repl-buffer-name))))) ;;; Location References @@ -1527,8 +1530,6 @@ constructs." "Add a REPL shortcut command, defined by NAME and HANDLER." (puthash name handler cider-repl-shortcuts)) -(declare-function cider-restart "cider-interaction") -(declare-function cider-quit "cider-interaction") (declare-function cider-toggle-trace-ns "cider-interaction") (declare-function cider-undef "cider-interaction") (declare-function cider-browse-ns "cider-browse-ns") @@ -1560,7 +1561,7 @@ constructs." (cider-repl-add-shortcut "test-project-with-filters" (lambda () (interactive) (cider-test-run-project-tests 'prompt-for-filters))) (cider-repl-add-shortcut "test-report" #'cider-test-show-report) (cider-repl-add-shortcut "run" #'cider-run) -(cider-repl-add-shortcut "conn-info" #'cider-display-connection-info) +(cider-repl-add-shortcut "conn-info" #'cider-describe-current-connection) (cider-repl-add-shortcut "conn-rotate" #'cider-rotate-default-connection) (cider-repl-add-shortcut "hasta la vista" #'cider-quit) (cider-repl-add-shortcut "adios" #'cider-quit) @@ -1618,7 +1619,6 @@ constructs." (declare-function cider-toggle-trace-ns "cider-interaction") (declare-function cider-toggle-trace-var "cider-interaction") (declare-function cider-find-resource "cider-interaction") -(declare-function cider-restart "cider-interaction") (declare-function cider-find-ns "cider-interaction") (declare-function cider-find-keyword "cider-interaction") (declare-function cider-switch-to-last-clojure-buffer "cider-mode") @@ -1657,10 +1657,11 @@ constructs." (define-key map (kbd "C-c C-c") #'cider-interrupt) (define-key map (kbd "C-c C-m") #'cider-macroexpand-1) (define-key map (kbd "C-c M-m") #'cider-macroexpand-all) + (define-key map (kbd "C-c C-s") #'sesman-map) (define-key map (kbd "C-c C-z") #'cider-switch-to-last-clojure-buffer) (define-key map (kbd "C-c M-o") #'cider-repl-switch-to-other) (define-key map (kbd "C-c M-s") #'cider-selector) - (define-key map (kbd "C-c M-d") #'cider-display-connection-info) + (define-key map (kbd "C-c M-d") #'cider-describe-current-connection) (define-key map (kbd "C-c C-q") #'cider-quit) (define-key map (kbd "C-c M-i") #'cider-inspect) (define-key map (kbd "C-c M-p") #'cider-repl-history) @@ -1670,6 +1671,13 @@ constructs." (define-key map (kbd "C-x C-e") #'cider-eval-last-sexp) (define-key map (kbd "C-c C-r") 'clojure-refactor-map) (define-key map (kbd "C-c C-v") 'cider-eval-commands-map) + (define-key map (kbd "C-c M-j") #'cider-jack-in-clojure) + (define-key map (kbd "C-c M-J") #'cider-jack-in-clojurescript) + (define-key map (kbd "C-c M-c") #'cider-connect-clojure) + (define-key map (kbd "C-c M-C") #'cider-connect-clojurescript) + (define-key map (kbd "C-c M-s") #'cider-connect-sibling-clojure) + (define-key map (kbd "C-c M-S") #'cider-connect-sibling-clojurescript) + (define-key map (string cider-repl-shortcut-dispatch-char) #'cider-repl-handle-shortcut) (easy-menu-define cider-repl-mode-menu map "Menu for CIDER's REPL mode" @@ -1701,7 +1709,6 @@ constructs." ["Refresh loaded code" cider-refresh] "--" ["Set REPL ns" cider-repl-set-ns] - ["Set REPL type" cider-repl-set-type] ["Toggle pretty printing" cider-repl-toggle-pretty-printing] ["Require REPL utils" cider-repl-require-repl-utils] "--" @@ -1722,7 +1729,7 @@ constructs." "--" ["Interrupt evaluation" cider-interrupt] "--" - ["Connection info" cider-display-connection-info] + ["Connection info" cider-describe-current-connection] "--" ["Close ancillary buffers" cider-close-ancillary-buffers] ["Quit" cider-quit] @@ -1757,6 +1764,7 @@ constructs." (clojure-mode-variables) (clojure-font-lock-setup) (font-lock-add-keywords nil cider--static-font-lock-keywords) + (setq-local sesman-system 'CIDER) (setq-local font-lock-fontify-region-function (cider-repl-wrap-fontify-function font-lock-fontify-region-function)) (setq-local font-lock-unfontify-region-function diff --git a/cider-resolve.el b/cider-resolve.el index 454663b86..5c966eede 100644 --- a/cider-resolve.el +++ b/cider-resolve.el @@ -72,8 +72,8 @@ (defun cider-resolve--get-in (&rest keys) "Return (nrepl-dict-get-in cider-repl-ns-cache KEYS)." - (when cider-connections - (with-current-buffer (cider-current-connection) + (when-let* ((conn (cider-current-connection))) + (with-current-buffer conn (nrepl-dict-get-in cider-repl-ns-cache keys)))) (defun cider-resolve-alias (ns alias) diff --git a/cider-scratch.el b/cider-scratch.el index 2dded9fbd..5838a6ef3 100644 --- a/cider-scratch.el +++ b/cider-scratch.el @@ -44,11 +44,7 @@ '("Clojure Interaction" (["Eval and print last sexp" #'cider-eval-print-last-sexp] "--" - ["Reset" #'cider-scratch-reset] - "--" - ["Set buffer connection" #'cider-assoc-buffer-with-connection] - ["Toggle buffer connection" #'cider-toggle-buffer-connection] - ["Reset buffer connection" #'cider-clear-buffer-local-connection]))) + ["Reset" #'cider-scratch-reset]))) map)) (defconst cider-scratch-buffer-name "*cider-scratch*") diff --git a/cider-selector.el b/cider-selector.el index dc6b4ea2b..a02b3ee9c 100644 --- a/cider-selector.el +++ b/cider-selector.el @@ -138,7 +138,7 @@ is chosen. The returned buffer is selected with (def-cider-selector-method ?r "Current REPL buffer." - (cider-current-repl-buffer)) + (cider-current-connection)) (def-cider-selector-method ?n "Connections browser buffer." diff --git a/cider-util.el b/cider-util.el index a6da4e115..ece165845 100644 --- a/cider-util.el +++ b/cider-util.el @@ -689,7 +689,7 @@ through a stack of help buffers. Variables `help-back-label' and "Press to read Clojure code from the minibuffer and inspect its result." "Press <\\[cider-refresh]> to reload modified and unloaded namespaces." "You can define Clojure functions to be called before and after `cider-refresh' (see `cider-refresh-before-fn' and `cider-refresh-after-fn'." - "Press <\\[cider-display-connection-info]> to view information about the connection." + "Press <\\[cider-describe-current-connection]> to view information about the connection." "Press <\\[cider-undef]> to undefine a symbol in the current namespace." "Press <\\[cider-interrupt]> to interrupt an ongoing evaluation." "Use to see every possible setting you can customize." diff --git a/cider.el b/cider.el index 6bc9b1a38..8c7857372 100644 --- a/cider.el +++ b/cider.el @@ -51,10 +51,13 @@ ;;; Usage: -;; M-x cider-jack-in +;; M-x cider-jack-in-clj ;; M-x cider-jack-in-cljs ;; -;; M-x cider-connect +;; M-x cider-connect-sibling-clj +;; M-x cider-connect-sibling-cljs +;; +;; M-x cider-connect-clj ;; M-x cider-connect-cljs ;;; Code: @@ -67,23 +70,10 @@ :link '(url-link :tag "Online Manual" "https://cider.readthedocs.io") :link '(emacs-commentary-link :tag "Commentary" "cider")) -(defcustom cider-prompt-for-project-on-connect 'when-needed - "Controls whether to prompt for associated project on `cider-connect'. - -When set to when-needed, the project will be derived from the buffer you're -visiting, when invoking `cider-connect'. -When set to t, you'll always to prompted to select the matching project. -When set to nil, you'll never be prompted to select a project and no -project inference will take place." - :type '(choice (const :tag "always" t) - (const when-needed) - (const :tag "never" nil)) - :group 'cider - :package-version '(cider . "0.10.0")) - (require 'cider-client) (require 'cider-eldoc) (require 'cider-repl) +(require 'cider-connection) (require 'cider-mode) (require 'cider-common) (require 'subr-x) @@ -381,9 +371,14 @@ Throws an error if PROJECT-TYPE is unknown. Known types are (cider-add-to-alist 'cider-jack-in-dependencies "org.clojure/tools.nrepl" "0.2.13") +(defvar cider-jack-in-cljs-dependencies nil + "List of dependencies where elements are lists of artifact name and version. +Added to `cider-jack-in-dependencies' when doing `cider-jack-in-cljs'.") +(put 'cider-jack-in-cljs-dependencies 'risky-local-variable t) +(cider-add-to-alist 'cider-jack-in-cljs-dependencies "cider/piggieback" "0.3.5") + (defvar cider-jack-in-dependencies-exclusions nil "List of exclusions for jack in dependencies. - Elements of the list are artifact name and list of exclusions to apply for the artifact.") (put 'cider-jack-in-dependencies-exclusions 'risky-local-variable t) (cider-add-to-alist 'cider-jack-in-dependencies-exclusions @@ -391,7 +386,6 @@ Elements of the list are artifact name and list of exclusions to apply for the a (defcustom cider-jack-in-auto-inject-clojure nil "Version of clojure to auto-inject into REPL. - If nil, do not inject Clojure into the REPL. If `latest', inject `cider-latest-clojure-version', which should approximate to the most recent version of Clojure. If `minimal', inject `cider-minimum-clojure-version', @@ -419,6 +413,12 @@ want to inject some middleware only when within a project context.)") (cider-add-to-alist 'cider-jack-in-lein-plugins "cider/cider-nrepl" (upcase cider-version)) +(defvar cider-jack-in-cljs-lein-plugins nil + "List of Leiningen plugins to be injected at jack-in. +Added to `cider-jack-in-lein-plugins' (which see) when doing +`cider-jack-in-cljs'.") +(put 'cider-jack-in-cljs-lein-plugins 'risky-local-variable t) + (defun cider-jack-in-normalized-lein-plugins () "Return a normalized list of Leiningen plugins to be injected. See `cider-jack-in-lein-plugins' for the format, except that the list @@ -444,6 +444,13 @@ the middlewares should actually be injected.") (put 'cider-jack-in-nrepl-middlewares 'risky-local-variable t) (add-to-list 'cider-jack-in-nrepl-middlewares "cider.nrepl/cider-middleware") +(defvar cider-jack-in-cljs-nrepl-middlewares nil + "List of Clojure variable names. +Added to `cider-jack-in-nrepl-middlewares' (which see) when doing +`cider-jack-in-cljs'.") +(put 'cider-jack-in-cljs-nrepl-middlewares 'risky-local-variable t) +(add-to-list 'cider-jack-in-cljs-nrepl-middlewares "cider.piggieback/wrap-cljs-repl") + (defun cider-jack-in-normalized-nrepl-middlewares () "Return a normalized list of middleware variable names. See `cider-jack-in-nrepl-middlewares' for the format, except that the list @@ -556,7 +563,6 @@ Does so by concatenating GLOBAL-OPTS, DEPENDENCIES finally PARAMS." (defun cider-add-clojure-dependencies-maybe (dependencies) "Return DEPENDENCIES with an added Clojure dependency if requested. - See also `cider-jack-in-auto-inject-clojure'." (if cider-jack-in-auto-inject-clojure (if (consp cider-jack-in-auto-inject-clojure) @@ -615,7 +621,6 @@ dependencies." (defcustom cider-check-cljs-repl-requirements t "When non-nil will run the requirement checks for the different cljs repls. - Generally you should not disable this unless you run into some faulty check." :type 'boolean :safe #'booleanp @@ -674,7 +679,6 @@ Generally you should not disable this unless you run into some faulty check." (defun cider-shadow-cljs-init-form () "Generate the init form for a shadow-cljs REPL. - We have to prompt the user to select a build, that's why this is a command, not just a string." (let ((form "(do (require '[shadow.cljs.devtools.api :as shadow]) (shadow/watch :%s) (shadow/nrepl-select :%s))") @@ -699,7 +703,6 @@ Figwheel for details." (defun cider-custom-cljs-repl-init-form () "Prompt for a form that would start a ClojureScript REPL. - The supplied string will be wrapped in a do form if needed." (let ((form (read-from-minibuffer "Please, provide a form to start a ClojureScript REPL: "))) ;; TODO: We should probably make this more robust (e.g. by using a regexp or @@ -751,7 +754,6 @@ It's intended to be used in your Emacs config." (defcustom cider-default-cljs-repl nil "The default ClojureScript REPL to start. - This affects commands like `cider-jack-in-clojurescript'. Generally it's intended to be set via .dir-locals.el for individual projects, as its relatively unlikely you'd like to use the same type of REPL in each project @@ -788,13 +790,17 @@ you're working on." repl-form) (user-error "No ClojureScript REPL type %s found. Please make sure that `cider-cljs-repl-types' has an entry for it" repl-type))) -(defun cider-verify-cljs-repl-requirements (repl-type) +(defun cider-verify-cljs-repl-requirements (&optional repl-type) "Verify that the requirements for REPL-TYPE are met." - (when-let* ((fun (nth 2 (seq-find - (lambda (entry) - (eq (car entry) repl-type)) - cider-cljs-repl-types)))) - (funcall fun))) + (let ((repl-type (or repl-type + cider-default-cljs-repl + (cider-select-cljs-repl)))) + + (when-let* ((fun (nth 2 (seq-find + (lambda (entry) + (eq (car entry) repl-type)) + cider-cljs-repl-types)))) + (funcall fun)))) (defun cider--offer-to-open-app-in-browser (server-buffer) "Look for a server address in SERVER-BUFFER and offer to open it." @@ -807,61 +813,6 @@ you're working on." (when (y-or-n-p (format "Visit ‘%s’ in a browser? " url)) (browse-url url))))))) -(defun cider-create-sibling-cljs-repl (client-buffer) - "Create a ClojureScript REPL with the same server as CLIENT-BUFFER. -The new buffer will correspond to the same project as CLIENT-BUFFER, which -should be the regular Clojure REPL started by the server process filter. - -Normally this would prompt for the ClojureScript REPL to start (e.g. Node, -Figwheel, etc), unless you've set `cider-default-cljs-repl'." - (interactive (list (cider-current-connection))) - ;; We can't start a ClojureScript REPL without ClojureScript - (when cider-check-cljs-repl-requirements - (cider-verify-clojurescript-is-present)) - ;; Load variables in .dir-locals.el into the server process buffer, so - ;; cider-default-cljs-repl can be set for each project individually. - (hack-local-variables) - (let* ((cljs-repl-type (or cider-default-cljs-repl - (cider-select-cljs-repl))) - (cljs-repl-form (cider-cljs-repl-form cljs-repl-type))) - (when cider-check-cljs-repl-requirements - (cider-verify-cljs-repl-requirements cljs-repl-type)) - ;; if all the requirements are met we can finally proceed with starting - ;; the ClojureScript REPL for `cljs-repl-type' - (let* ((nrepl-repl-buffer-name-template "*cider-repl%s(cljs)*") - (nrepl-create-client-buffer-function #'cider-repl-create) - (nrepl-use-this-as-repl-buffer 'new) - (client-process-args (with-current-buffer client-buffer - (unless (or nrepl-server-buffer nrepl-endpoint) - (error "This is not a REPL buffer, is there a REPL active?")) - (list (car nrepl-endpoint) - (elt nrepl-endpoint 1) - (when (buffer-live-p nrepl-server-buffer) - (get-buffer-process nrepl-server-buffer))))) - (cljs-proc (apply #'nrepl-start-client-process client-process-args)) - (cljs-buffer (process-buffer cljs-proc))) - (with-current-buffer cljs-buffer - ;; The new connection has now been bumped to the top, but it's still a - ;; Clojure REPL! Additionally, some ClojureScript REPLs can actually take - ;; a while to start (some even depend on the user opening a browser). - ;; Meanwhile, this REPL will gladly receive requests in place of the - ;; original Clojure REPL. Our solution is to bump the original REPL back - ;; up the list, so it takes priority on Clojure requests. - (cider-make-connection-default client-buffer) - (cider-repl-set-type "cljs") - (pcase cider-cljs-repl-types - (`(,name ,_ ,info) - (message "Starting a %s REPL%s" name (or info "")))) - ;; So far we have just another Clojure REPL. It's time to convert it - ;; to a ClojureScript REPL with a magic incantation. - (cider-nrepl-send-request - `("op" "eval" - "ns" ,(cider-current-ns) - "code" ,cljs-repl-form) - (cider-repl-handler (current-buffer))) - (when cider-offer-to-open-cljs-app-in-browser - (cider--offer-to-open-app-in-browser nrepl-server-buffer)))))) - (defun cider--select-zombie-buffer (repl-buffers) "Return a zombie buffer from REPL-BUFFERS, or nil if none exists." (when-let* ((zombie-buffs (seq-remove #'get-buffer-process repl-buffers))) @@ -874,39 +825,11 @@ Figwheel, etc), unless you've set `cider-default-cljs-repl'." (mapcar #'buffer-name zombie-buffs) nil t))))) -(defun cider-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." - (if-let* ((repl-buffers (cider-repl-buffers)) - (exact-buff (seq-find - (lambda (buff) - (with-current-buffer buff - (or (and endpoint - (equal endpoint nrepl-endpoint)) - (and project-directory - (equal project-directory nrepl-project-dir))))) - repl-buffers))) - (if (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)) - 'new) - exact-buff) - (or (cider--select-zombie-buffer repl-buffers) 'new))) + +;;; Barefoot Connectors -;;;###autoload -(defun cider-jack-in (&optional prompt-project cljs-too) - "Start an 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. -If CLJS-TOO is non-nil, also start a ClojureScript REPL session with its -own buffer." - (interactive "P") +(defun cider--jack-in (prompt-project on-port-callback) + (declare (indent 1)) (let* ((project-type (cider-project-type)) (command (cider-jack-in-command project-type)) (command-resolved (cider-jack-in-resolve-command project-type)) @@ -918,115 +841,180 @@ own buffer." (project-dir (clojure-project-dir (or project (cider-current-dir)))) (params (if prompt-project - (read-string (format "nREPL server command: %s " - command-params) + (read-string (format "nREPL server command: %s " command-params) command-params) command-params)) (params (if cider-inject-dependencies-at-jack-in (cider-inject-jack-in-dependencies command-global-opts params project-type) - params)) - - (cmd (format "%s %s" command params))) + params))) (if (or project-dir cider-allow-jack-in-without-project) - (progn - (when (or project-dir - (eq cider-allow-jack-in-without-project t) - (and (null project-dir) - (eq cider-allow-jack-in-without-project 'warn) - (y-or-n-p "Are you sure you want to run `cider-jack-in' without a Clojure project? "))) - (when-let* ((repl-buff (cider-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 - (when cljs-too #'cider-create-sibling-cljs-repl)))))) + (when (or project-dir + (eq cider-allow-jack-in-without-project t) + (and (null project-dir) + (eq cider-allow-jack-in-without-project 'warn) + (y-or-n-p "Are you sure you want to run `cider-jack-in' without a Clojure project? "))) + (let* ((cmd (format "%s %s" command-resolved params))) + (nrepl-start-server-process project-dir cmd on-port-callback))) (user-error "`cider-jack-in' is not allowed without a Clojure project"))) (user-error "The %s executable isn't on your `exec-path'" command)))) -(defvar cider-jack-in-cljs-dependencies nil - "List of dependencies where elements are lists of artifact name and version. -Added to `cider-jack-in-dependencies' when doing `cider-jack-in-cljs'.") -(put 'cider-jack-in-cljs-dependencies 'risky-local-variable t) -(cider-add-to-alist 'cider-jack-in-cljs-dependencies "cider/piggieback" "0.3.6") +(defun cider--check-cljs (&optional repl-type no-error) + (if no-error + (condition-case ex + (progn + (cider-verify-clojurescript-is-present) + (cider-verify-cljs-repl-requirements repl-type) + t) + (error + (message "Invalid CLJS dependency: %S" ex) + nil)) + (cider-verify-clojurescript-is-present) + (cider-verify-cljs-repl-requirements repl-type) + t)) + +(defun cider--cljs-init-hook-builder (repl-type) + (lambda () + (cider--check-cljs repl-type) + (cider-nrepl-send-request + (list "op" "eval" + "ns" (cider-current-ns) + "code" (cider-cljs-repl-form repl-type)) + (cider-repl-handler (current-buffer))) + (when (and (buffer-live-p nrepl-server-buffer) + cider-offer-to-open-cljs-app-in-browser) + (cider--offer-to-open-app-in-browser nrepl-server-buffer)))) -(defvar cider-jack-in-cljs-lein-plugins nil - "List of Leiningen plugins to be injected at jack-in. -Added to `cider-jack-in-lein-plugins' when doing `cider-jack-in-cljs'. -Each element is a list of artifact name and version, followed optionally by -keyword arguments. The only keyword argument currently accepted is -`:predicate', which should be given a function that takes the list (name, -version, and keyword arguments) and returns non-nil to indicate that the -plugin should actually be injected. (This is useful primarily for packages -that extend CIDER, not for users. For example, a refactoring package might -want to inject some middleware only when within a project context.)") -(put 'cider-jack-in-cljs-lein-plugins 'risky-local-variable t) -(defvar cider-jack-in-cljs-nrepl-middlewares nil - "List of Clojure variable names. -Added to `cider-jack-in-nrepl-middlewares' when doing `cider-jack-in-cljs'. -Each of these Clojure variables should hold a vector of nREPL middlewares. -Instead of a string, an element can be a list containing a string followed -by optional keyword arguments. The only keyword argument currently -accepted is `:predicate', which should be given a function that takes the -list (string and keyword arguments) and returns non-nil to indicate that -the middlewares should actually be injected.") -(put 'cider-jack-in-cljs-nrepl-middlewares 'risky-local-variable t) -(add-to-list 'cider-jack-in-cljs-nrepl-middlewares "cider.piggieback/wrap-cljs-repl") + +;;; User Level Connectors ;;;###autoload -(defun cider-jack-in-clojurescript (&optional prompt-project) - "Start an nREPL server and connect to it both Clojure and ClojureScript REPLs. -If PROMPT-PROJECT is t, then prompt for the project for which to -start the server." +(defun cider-jack-in-clj (&optional prompt-project) + "Start an nREPL server for the current project and connect to it. +Prompt for the project when PROMPT-PROJECT is non-nil." + (interactive "P") + (cider--jack-in prompt-project + (lambda (server-buffer) + (cider-connect-sibling-clj server-buffer)))) + +;;;###autoload +(defun cider-jack-in-cljs (&optional prompt-project) + "Start an nREPL server for the current project and connect to it. +Prompt for the project when PROMPT-PROJECT is non-nil." (interactive "P") - ;; We override the standard jack-in deps to inject additional ClojureScript-specific deps (let ((cider-jack-in-dependencies (append cider-jack-in-dependencies cider-jack-in-cljs-dependencies)) (cider-jack-in-lein-plugins (append cider-jack-in-lein-plugins cider-jack-in-cljs-lein-plugins)) (cider-jack-in-nrepl-middlewares (append cider-jack-in-nrepl-middlewares cider-jack-in-cljs-nrepl-middlewares))) - (cider-jack-in prompt-project 'cljs-too))) + (cider--jack-in prompt-project + (lambda (server-buffer) + (cider-connect-sibling-cljs server-buffer))))) ;;;###autoload -(defalias 'cider-jack-in-cljs #'cider-jack-in-clojurescript) +(defun cider-jack-in-cljcljs (&optional prompt-project soft-cljs-start) + "Start an nREPL server and connect with clj and cljs REPLs. +Prompt for the project when PROMPT-PROJECT is non-nil. When +SOFT-CLJS-START is non-nil, start cljs REPL only when the clojurescript +dependencies are met." + (interactive "P") + (let ((cider-jack-in-dependencies (append cider-jack-in-dependencies cider-jack-in-cljs-dependencies)) + (cider-jack-in-lein-plugins (append cider-jack-in-lein-plugins cider-jack-in-cljs-lein-plugins)) + (cider-jack-in-nrepl-middlewares (append cider-jack-in-nrepl-middlewares cider-jack-in-cljs-nrepl-middlewares))) + (cider--jack-in prompt-project + (lambda (server-buffer) + (let ((clj-repl (cider-connect-sibling-clj server-buffer))) + (if soft-cljs-start + (when (cider--check-cljs) + (cider-connect-sibling-cljs clj-repl)) + (cider-connect-sibling-cljs clj-repl))))))) ;;;###autoload -(defun cider-connect (host port &optional project-dir) - "Connect to an nREPL server identified by HOST and PORT. -Create REPL buffer and start an nREPL client connection. +(defun cider-connect-sibling-clj (other-repl) + "Create a Clojure REPL with the same server as OTHER-REPL. +OTHER-REPL can also be a server buffer, in which case a new session with a +REPL for that server is created." + (interactive (list (cider-current-connection))) + (cider--connect + (let ((ses-name (unless (nrepl-server-p other-repl) + (car (sesman-get-session-for-object 'CIDER other-repl))))) + (thread-first (cider--gather-connect-params other-repl) + (plist-put :repl-type "clj") + (plist-put :session-name ses-name) + (plist-put :repl-init-function nil))))) -When the optional param PROJECT-DIR is present, the connection -gets associated with it." +;;;###autoload +(defun cider-connect-sibling-cljs (other-repl) + "Create a ClojureScript REPL with the same server as OTHER-REPL. +Normally this would prompt for the ClojureScript REPL to start (e.g. Node, +Figwheel, etc), unless you've set `cider-default-cljs-repl'. OTHER-REPL +can also be a server buffer, in which case a new session with a REPL for +that server is created." + (interactive (list (cider-current-connection))) + (let ((cljs-repl-type (or cider-default-cljs-repl + (cider-select-cljs-repl))) + (ses-name (unless (nrepl-server-p other-repl) + (sesman-get-session-name-for-object 'CIDER other-repl)))) + (cider--connect + (thread-first (cider--gather-connect-params other-repl) + (plist-put :repl-type "cljs") + (plist-put :session-name ses-name) + (plist-put :repl-init-function (cider--cljs-init-hook-builder cljs-repl-type)))))) + +;;;###autoload +(defun cider-connect-clj (host port) + "Connect to an nREPL server identified by HOST and PORT. +Create REPL buffer and start an nREPL client connection." (interactive (cider-select-endpoint)) - (when-let* ((repl-buff (cider-find-reusable-repl-buffer `(,host ,port) nil))) - (let* ((nrepl-create-client-buffer-function #'cider-repl-create) - (nrepl-use-this-as-repl-buffer repl-buff) - (conn (process-buffer (nrepl-start-client-process host port)))) - (with-current-buffer conn - (setq cider-connection-created-with 'connect)) - (if project-dir - (cider-assoc-project-with-connection project-dir conn) - (let ((project-dir (clojure-project-dir))) - (cond - ;; associate only if we're in a project - ((and project-dir (null cider-prompt-for-project-on-connect)) (cider-assoc-project-with-connection project-dir conn)) - ;; associate if we're in a project, prompt otherwise - ((eq cider-prompt-for-project-on-connect 'when-needed) (cider-assoc-project-with-connection project-dir conn)) - ;; always prompt - (t (cider-assoc-project-with-connection nil conn))))) - conn))) + (cider--connect + (list :host host :port port + :repl-type "clj" + :repl-init-function nil + :session-name nil + :project-dir (or (clojure-project-dir (cider-current-dir)) + default-directory)))) ;;;###autoload -(defun cider-connect-clojurescript () +(defun cider-connect-cljs (host port) "Connect to a ClojureScript REPL. - It just delegates pretty much everything to `cider-connect' and just sets the appropriate REPL type in the end." - (interactive) - (when-let* ((conn (call-interactively #'cider-connect))) - (with-current-buffer conn - (cider-repl-set-type "cljs")))) + (interactive (cider-select-endpoint)) + (let ((cljs-repl-type (or cider-default-cljs-repl + (cider-select-cljs-repl)))) + (cider--connect + (list :host host :port port + :repl-type "cljs" + :repl-init-function (cider--cljs-init-hook-builder cljs-repl-type) + :session-name nil + :project-dir (or (clojure-project-dir (cider-current-dir)) + default-directory))))) + +;;;###autoload +(defun cider-connect-cljcljs (host port) + (interactive (cider-select-endpoint)) + (thread-first (cider-connect-clj host port) + (cider-connect-sibling-cljs))) + + +;;; Aliases + + ;;;###autoload +(defalias 'cider-jack-in #'cider-jack-in-clj) + ;;;###autoload +(defalias 'cider-jack-in-clojure #'cider-jack-in-clj) +;;;###autoload +(defalias 'cider-jack-in-clojurescript #'cider-jack-in-cljs) + +;;;###autoload +(defalias 'cider-connect #'cider-connect-clj) +;;;###autoload +(defalias 'cider-connect-clojure #'cider-connect-clj) +;;;###autoload +(defalias 'cider-connect-clojurescript #'cider-connect-cljs) ;;;###autoload -(defalias 'cider-connect-cljs #'cider-connect-clojurescript) +(defalias 'cider-connect-sibling-clojure #'cider-connect-sibling-clj) +;;;###autoload +(defalias 'cider-connect-sibling-clojurescript #'cider-connect-sibling-cljs) (defun cider-current-host () "Retrieve the current host." @@ -1034,6 +1022,9 @@ the appropriate REPL type in the end." (file-remote-p buffer-file-name 'host) "localhost")) + +;;; Helpers + (defun cider-select-endpoint () "Interactively select the host and port to connect to." (dolist (endpoint cider-known-endpoints) @@ -1165,7 +1156,6 @@ choose." ;; TODO: Implement a check for command presence over tramp (defun cider--resolve-command (command) "Find COMMAND on `exec-path' if possible, or return nil. - In case `default-directory' is non-local we assume the command is available." (when-let* ((command (or (and (file-remote-p default-directory) command) (executable-find command) @@ -1224,47 +1214,16 @@ available) and the matching REPL buffer." (cider-nrepl-send-request '("op" "out-subscribe") (cider-interactive-eval-handler (current-buffer)))) -(defun cider--connected-handler () - "Handle CIDER initialization after nREPL connection has been established. -This function is appended to `nrepl-connected-hook' in the client process -buffer." - ;; `nrepl-connected-hook' is run in the connection buffer - - ;; `cider-enlighten-mode' changes eval to include the debugger, so we inhibit - ;; it here as the debugger isn't necessarily initialized yet - (let ((cider-enlighten-mode nil)) - (cider-make-connection-default (current-buffer)) - (cider-repl-init (current-buffer)) - (cider--check-required-nrepl-version) - (cider--check-clojure-version-supported) - (cider--check-middleware-compatibility) - (when cider-redirect-server-output-to-repl - (cider--subscribe-repl-to-server-out)) - (when cider-auto-mode - (cider-enable-on-existing-clojure-buffers)) - ;; Middleware on cider-nrepl's side is deferred until first usage, but - ;; loading middleware concurrently can lead to occasional "require" issues - ;; (likely a Clojure bug). Thus, we load the heavy debug middleware towards - ;; the end, allowing for the faster "server-out" middleware to load - ;; first. - (cider--debug-init-connection) - (run-hooks 'cider-connected-hook))) - -(defun cider--disconnected-handler () - "Cleanup after nREPL connection has been lost or closed. -This function is appended to `nrepl-disconnected-hook' in the client -process buffer." - ;; `nrepl-connected-hook' is run in the connection buffer - (cider-possibly-disable-on-existing-clojure-buffers) - (run-hooks 'cider-disconnected-hook)) - ;;;###autoload (eval-after-load 'clojure-mode '(progn - (define-key clojure-mode-map (kbd "C-c M-j") #'cider-jack-in) - (define-key clojure-mode-map (kbd "C-c M-J") #'cider-jack-in-clojurescript) - (define-key clojure-mode-map (kbd "C-c M-c") #'cider-connect) - (define-key clojure-mode-map (kbd "C-c M-C") #'cider-connect-clojurescript))) + (define-key clojure-mode-map (kbd "C-c M-j") #'cider-jack-in-clj) + (define-key clojure-mode-map (kbd "C-c M-J") #'cider-jack-in-cljs) + (define-key clojure-mode-map (kbd "C-c M-c") #'cider-connect-clj) + (define-key clojure-mode-map (kbd "C-c M-C") #'cider-connect-cljs) + (define-key clojure-mode-map (kbd "C-c M-s") #'cider-connect-sibling-clj) + (define-key clojure-mode-map (kbd "C-c M-S") #'cider-connect-sibling-cljs) + (define-key clojure-mode-map (kbd "C-c C-s") 'sesman-map))) (provide 'cider) diff --git a/doc/cider-refcard.tex b/doc/cider-refcard.tex index 6d877bd5e..7555c453e 100644 --- a/doc/cider-refcard.tex +++ b/doc/cider-refcard.tex @@ -125,7 +125,7 @@ \section{REPL control} \item[C-c M-p] cider-insert-last-sexp-in-repl \item[C-c C-z] cider-switch-to-repl-buffer \item[C-c M-o] cider-find-and-clear-repl-buffer - \item[C-c M-d] cider-display-connection-info + \item[C-c M-d] cider-describe-current-connection \item[C-c M-r] cider-rotate-default-connection \item[C-c M-n] cider-repl-set-ns \item[C-c C-b] cider-interrupt diff --git a/doc/clojurescript.md b/doc/clojurescript.md index 958a331b3..897765de7 100644 --- a/doc/clojurescript.md +++ b/doc/clojurescript.md @@ -100,7 +100,7 @@ You can also modify the known ClojureScript REPLs on a per-project basis using ``` You can also create a ClojureScript REPL with the command -`cider-create-sibling-cljs-repl` in cases where you already have a +`cider-jack-in-sibling-clojurescript` in cases where you already have a Clojure REPL running. Continue reading for the additional setup needed for the various ClojureScript diff --git a/doc/interactive_programming.md b/doc/interactive_programming.md index ba5ce55c0..335715c43 100644 --- a/doc/interactive_programming.md +++ b/doc/interactive_programming.md @@ -37,8 +37,7 @@ Here's a list of `cider-mode`'s keybindings: `cider-switch-to-repl-buffer` |C-c C-z | Switch to the relevant REPL buffer. Use a prefix argument to change the namespace of the REPL buffer to match the currently visited source file. `cider-switch-to-repl-buffer` |C-u C-u C-c C-z | Switch to the REPL buffer based on a user prompt for a directory. `cider-load-buffer-and-switch-to-repl-buffer` |C-c M-z | Load (eval) the current buffer and switch to the relevant REPL buffer. Use a prefix argument to change the namespace of the REPL buffer to match the currently visited source file. -`cider-display-connection-info` |C-c M-d | Display default REPL connection details, including project directory name, buffer namespace, host and port. -`cider-rotate-default-connection` |C-c M-r | Rotate and display the default nREPL connection. +`cider-describe-current-connection |C-c M-d | Display default REPL connection details, including project directory name, buffer namespace, host and port. `cider-find-and-clear-repl-output` |C-c C-o | Clear the last output in the REPL buffer. With a prefix argument it will clear the entire REPL buffer, leaving only a prompt. Useful if you're running the REPL buffer in a side by side buffer. `cider-load-buffer` |C-c C-k | Load (eval) the current buffer. `cider-load-file` |C-c C-l | Load (eval) a Clojure file. @@ -67,7 +66,7 @@ Here's a list of `cider-mode`'s keybindings: `cider-find-ns` |C-c C-. | Jump to some namespace on the classpath. `cider-pop-back` |M-, | Return to your pre-jump location. `complete-symbol` |M-TAB | Complete the symbol at point. -`cider-quit` |C-c C-q | Quit the current nREPL connection. With a prefix argument it will quit all connections. +`cider-quit` |C-c C-q | Quit the current nREPL connection. There's no need to memorize this list. In any Clojure buffer with `cider-mode` active you'll have a CIDER menu available, which lists all the most important diff --git a/nrepl-client.el b/nrepl-client.el index 38e0cb530..4826072e4 100644 --- a/nrepl-client.el +++ b/nrepl-client.el @@ -133,30 +133,16 @@ When true some special buffers like the server buffer will be hidden." :type 'boolean :group 'nrepl) -(defcustom nrepl-prompt-to-kill-server-buffer-on-quit t - "If non-nil, prompt the user for confirmation before killing the nrepl server buffer and associated process." - :type 'boolean - :group 'nrepl) - -(defvar nrepl-create-client-buffer-function 'nrepl-create-client-buffer-default - "Name of a function that returns a client process buffer. -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.") - ;;; Buffer Local Declarations ;; These variables are used to track the state of nREPL connections -(defvar-local nrepl-client-buffers nil - "List of buffers connected to this server.") (defvar-local nrepl-connection-buffer nil) (defvar-local nrepl-server-buffer nil) (defvar-local nrepl-endpoint nil) (defvar-local nrepl-project-dir nil) +(defvar-local nrepl-is-server nil) +(defvar-local nrepl-server-command nil) (defvar-local nrepl-tunnel-buffer nil) (defvar-local nrepl-session nil @@ -194,7 +180,6 @@ To be used for tooling calls (i.e. completion, eldoc, etc)") (defconst nrepl-message-buffer-name-template "*nrepl-messages %s*") (defconst nrepl-error-buffer-name "*nrepl-error*") (defconst nrepl-repl-buffer-name-template "*cider-repl%s*") -(defconst nrepl-connection-buffer-name-template "*nrepl-connection%s*") (defconst nrepl-server-buffer-name-template "*nrepl-server%s*") (defconst nrepl-tunnel-buffer-name-template "*nrepl-tunnel%s*") @@ -205,26 +190,24 @@ To be used for tooling calls (i.e. completion, eldoc, etc)") (concat nrepl-buffer-name-separator designation) ""))) -(defun nrepl-make-buffer-name (buffer-name-template &optional project-dir host port dup-ok) +(defun nrepl-make-buffer-name (buffer-name-template &optional project-dir host port extras dup-ok) "Generate a buffer name using BUFFER-NAME-TEMPLATE. - If not supplied PROJECT-DIR, HOST and PORT 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. - -If optional DUP-OK is non-nil, the returned buffer is not \"uniquified\" by -`generate-new-buffer-name'." +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. EXTRAS is appended towards the end +of the name. If optional DUP-OK is non-nil, the returned buffer is not +\"uniquified\" by a call to `generate-new-buffer-name'." (let* ((project-dir (or project-dir nrepl-project-dir)) (project-name (when project-dir (file-name-nondirectory (directory-file-name project-dir)))) - (nrepl-proj-port (or port (cadr nrepl-endpoint))) + (nrepl-proj-port (or port (plist-get nrepl-endpoint :port))) (name (nrepl-format-buffer-name-template buffer-name-template - (concat (if project-name project-name (or host (car nrepl-endpoint))) + (concat (if project-name project-name (or host (plist-get nrepl-endpoint :host))) (if (and nrepl-proj-port nrepl-buffer-name-show-port) - (format ":%s" nrepl-proj-port) ""))))) + (format ":%s" nrepl-proj-port) "") + (if extras (format "(%s)" extras) ""))))) (if dup-ok name (generate-new-buffer-name name)))) @@ -233,12 +216,11 @@ If optional DUP-OK is non-nil, the returned buffer is not \"uniquified\" by "Apply a prefix to BUFFER-NAME that will hide the buffer." (concat (if nrepl-hide-special-buffers " " "") buffer-name)) -(defun nrepl-connection-buffer-name (&optional project-dir host port) - "Return the name of the connection buffer. -PROJECT-DIR, HOST and PORT are as in `/nrepl-make-buffer-name'." - (nrepl--make-hidden-name - (nrepl-make-buffer-name nrepl-connection-buffer-name-template - project-dir host port))) +(defun nrepl-repl-buffer-name (&optional project-dir host port dup-ok) + "Return the name of the repl buffer. +PROJECT-DIR, HOST and PORT are as in `nrepl-make-buffer-name'." + (nrepl-make-buffer-name nrepl-repl-buffer-name-template + project-dir host port cider-repl-type dup-ok)) (defun nrepl-connection-identifier (conn) "Return the string which identifies a connection CONN." @@ -264,8 +246,9 @@ PROJECT-DIR, HOST and PORT are as in `nrepl-make-buffer-name'." ;;; Utilities (defun nrepl-op-supported-p (op connection) "Return t iff the given operation OP is supported by the nREPL CONNECTION." - (with-current-buffer connection - (and nrepl-ops (nrepl-dict-get nrepl-ops op)))) + (when (buffer-live-p connection) + (with-current-buffer connection + (and nrepl-ops (nrepl-dict-get nrepl-ops op))))) (defun nrepl-aux-info (key connection) "Return KEY's aux info, as returned via the :describe op for CONNECTION." @@ -507,10 +490,10 @@ and kill the process buffer." (nrepl--clear-client-sessions client-buffer) (with-current-buffer client-buffer (run-hooks 'nrepl-disconnected-hook) - (when (buffer-live-p nrepl-server-buffer) - (with-current-buffer nrepl-server-buffer - (setq nrepl-client-buffers (delete client-buffer nrepl-client-buffers))) - (nrepl--maybe-kill-server-buffer nrepl-server-buffer)))))) + (let ((server-buffer nrepl-server-buffer)) + (when (buffer-live-p server-buffer) + (setq nrepl-server-buffer nil) + (nrepl--maybe-kill-server-buffer server-buffer))))))) ;;; Network @@ -618,6 +601,7 @@ If NO-ERROR is non-nil, show messages instead of throwing an error." ;;; Client: Process Handling + (defun nrepl--kill-process (proc) "Kill PROC using the appropriate, os specific way. Implement a workaround to clean up an orphaned JVM process left around @@ -626,35 +610,40 @@ after exiting the REPL on some windows machines." (interrupt-process proc) (kill-process proc))) +(defun nrepl-kill-server-buffer (server-buf) + (when (buffer-live-p server-buf) + (let ((proc (get-buffer-process server-buf))) + (when (process-live-p proc) + (set-process-query-on-exit-flag proc nil) + (nrepl--kill-process proc)) + (kill-buffer server-buf)))) + (defun nrepl--maybe-kill-server-buffer (server-buf) - "Kill SERVER-BUF and its process, subject to user confirmation. -Do nothing if there is a REPL connected to that server." - (with-current-buffer server-buf - ;; Don't kill the server if there is a REPL connected to it. - (when (and (not nrepl-client-buffers) - (or (not nrepl-prompt-to-kill-server-buffer-on-quit) - (y-or-n-p "Also kill server process and buffer? "))) - (let ((proc (get-buffer-process server-buf))) - (when (process-live-p proc) - (set-process-query-on-exit-flag proc nil) - (nrepl--kill-process proc)) - (kill-buffer server-buf))))) - -;; `nrepl-start-client-process' is called from `nrepl-server-filter'. It -;; starts the client process described by `nrepl-client-filter' and -;; `nrepl-client-sentinel'. -(defun nrepl-start-client-process (&optional host port server-proc) + "Kill SERVER-BUF and its process. +Do not kill the server if there is a REPL connected to that server." + (when (buffer-live-p server-buf) + (with-current-buffer server-buf + ;; Don't kill if there is at least one REPL connected to it. + (when (not (seq-find (lambda (b) + (eq (buffer-local-value 'nrepl-server-buffer b) + server-buf)) + (buffer-list))) + (nrepl-kill-server-buffer server-buf))))) + +(defun nrepl-start-client-process (&optional host port server-proc buffer-builder) "Create new client process identified by HOST and PORT. In remote buffers, HOST and PORT are taken from the current tramp connection. SERVER-PROC must be a running nREPL server process within -Emacs. This function creates connection buffer by a call to -`nrepl-create-client-buffer-function'. Return newly created client +Emacs. BUFFER-BUILDER is a function of one argument (endpoint returned by +`nrepl-connect') which returns a client buffer (defaults to +`nrepl-default-client-buffer-builder'). Return the newly created client process." (let* ((endpoint (nrepl-connect host port)) (client-proc (plist-get endpoint :proc)) (host (plist-get endpoint :host)) (port (plist-get endpoint :port)) - (client-buf (funcall nrepl-create-client-buffer-function endpoint))) + (builder (or buffer-builder nrepl-default-client-buffer-builder)) + (client-buf (funcall builder endpoint))) (set-process-buffer client-proc client-buf) @@ -669,7 +658,7 @@ process." (when-let* ((server-buf (and server-proc (process-buffer server-proc)))) (setq nrepl-project-dir (buffer-local-value 'nrepl-project-dir server-buf) nrepl-server-buffer server-buf)) - (setq nrepl-endpoint `(,host ,port) + (setq nrepl-endpoint endpoint nrepl-tunnel-buffer (when-let* ((tunnel (plist-get endpoint :tunnel))) (process-buffer tunnel)) nrepl-pending-requests (make-hash-table :test 'equal) @@ -684,7 +673,6 @@ process." (defun nrepl--init-client-sessions (client) "Initialize CLIENT connection nREPL sessions. - We create two client nREPL sessions per connection - a main session and a tooling session. The main session is general purpose and is used for pretty much every request that needs a session. The tooling session is used only @@ -1017,39 +1005,41 @@ session." ;; nrepl communication client (`nrepl-client-filter') when the message "nREPL ;; server started on port ..." is detected. -(defvar-local nrepl-post-client-callback nil - "Function called after the client process is started. -Used by `nrepl-start-server-process'.") +;; internal variables used for state transfer between nrepl-start-server-process +;; and nrepl-server-filter. +(defvar-local nrepl-on-port-callback nil) + +(defun nrepl-server-p (buffer-or-process) + "Return t if BUFFER-OR-PROCESS is an nREPL server." + (let ((buffer (if (processp buffer-or-process) + (process-buffer buffer-or-process) + buffer-or-process))) + (buffer-local-value 'nrepl-is-server buffer))) -(defun nrepl-start-server-process (directory cmd &optional callback) +(defun nrepl-start-server-process (directory cmd on-port-callback) "Start nREPL server process in DIRECTORY using shell command CMD. -Return a newly created process. -Set `nrepl-server-filter' as the process filter, which starts REPL process -with its own buffer once the server has started. -If CALLBACK is non-nil, it should be function of 1 argument. Once the -client process is started, the function is called with the client buffer." +Return a newly created process. Set `nrepl-server-filter' as the process +filter, which starts REPL process with its own buffer once the server has +started. ON-PORT-CALLBACK is a function of one argument (server buffer) +which is called by the process filter once the port of the connection has +been determined." (let* ((default-directory (or directory default-directory)) - (serv-buf (get-buffer-create (generate-new-buffer-name - (nrepl-server-buffer-name directory)))) - (serv-proc (start-file-process-shell-command - "nrepl-server" serv-buf cmd))) - (set-process-filter serv-proc 'nrepl-server-filter) - (set-process-sentinel serv-proc 'nrepl-server-sentinel) - (set-process-coding-system serv-proc 'utf-8-unix 'utf-8-unix) + (serv-buf (get-buffer-create + (generate-new-buffer-name + (nrepl-server-buffer-name default-directory))))) (with-current-buffer serv-buf - (setq nrepl-project-dir directory) - (setq nrepl-post-client-callback callback) - ;; Ensure that `nrepl-start-client-process' sees right things. This - ;; causes warnings about making a local within a let-bind. This is safe - ;; as long as `serv-buf' is not the buffer where the let-binding was - ;; started. http://www.gnu.org/software/emacs/manual/html_node/elisp/Creating-Buffer_002dLocal.html - (setq-local 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)) + (setq nrepl-is-server t + nrepl-project-dir default-directory + nrepl-server-command cmd + nrepl-on-port-callback on-port-callback)) + (let ((serv-proc (start-file-process-shell-command + "nrepl-server" serv-buf cmd))) + (set-process-filter serv-proc 'nrepl-server-filter) + (set-process-sentinel serv-proc 'nrepl-server-sentinel) + (set-process-coding-system serv-proc 'utf-8-unix 'utf-8-unix) + (message "[nREPL] Starting server via %s..." + (propertize cmd 'face 'font-lock-keyword-face)) + serv-proc))) (defun nrepl-server-filter (process output) "Process nREPL server output from PROCESS contained in OUTPUT." @@ -1067,27 +1057,25 @@ client process is started, the function is called with the client buffer." (when moving (goto-char (process-mark process)) (when-let* ((win (get-buffer-window))) - (set-window-point win (point)))))) - ;; detect the port the server is listening on from its output - (when (string-match "nREPL server started on port \\([0-9]+\\)" output) - (let ((port (string-to-number (match-string 1 output)))) - (message "nREPL server started on %s" port) - (with-current-buffer server-buffer - (let* ((client-proc (nrepl-start-client-process nil port process)) - (client-buffer (process-buffer client-proc))) - (setq nrepl-client-buffers - (cons client-buffer - (delete client-buffer nrepl-client-buffers))) - - (when (functionp nrepl-post-client-callback) - (funcall nrepl-post-client-callback client-buffer))))))))) - -(declare-function cider--close-connection-buffer "cider-client") + (set-window-point win (point))))) + ;; detect the port the server is listening on from its output + (when (and (null nrepl-endpoint) + (string-match "[nREPL] Server started on port \\([0-9]+\\)" output)) + (let ((port (string-to-number (match-string 1 output)))) + (setq nrepl-endpoint (list :host nil :port port)) + (message "[nREPL] server started on %s" port) + (when nrepl-on-port-callback + (funcall nrepl-on-port-callback (process-buffer process))))))))) + +(declare-function cider--close-connection "cider-client") (defun nrepl-server-sentinel (process event) "Handle nREPL server PROCESS EVENT." (let* ((server-buffer (process-buffer process)) - (clients (buffer-local-value 'nrepl-client-buffers server-buffer)) + (clients (seq-filter (lambda (b) + (eq (buffer-local-value 'nrepl-server-buffer b) + server-buffer)) + (buffer-list))) (problem (if (and server-buffer (buffer-live-p server-buffer)) (with-current-buffer server-buffer (buffer-substring (point-min) (point-max))) @@ -1098,7 +1086,7 @@ client process is started, the function is called with the client buffer." ((string-match-p "^killed\\|^interrupt" event) nil) ((string-match-p "^hangup" event) - (mapc #'cider--close-connection-buffer clients)) + (mapc #'cider--close-connection clients)) ;; On Windows, a failed start sends the "finished" event. On Linux it sends ;; "exited abnormally with code 1". (t (error "Could not start nREPL server: %s" problem))))) @@ -1108,13 +1096,10 @@ client process is started, the function is called with the client buffer." (defcustom nrepl-log-messages nil "If non-nil, log protocol messages to an nREPL messages buffer. - -This is extremely useful for debug purposes, as it allows you to -inspect the communication between Emacs and an nREPL server. - -Enabling the logging might have a negative impact on performance, -so it's not recommended to keep it enabled unless you need to -debug something." +This is extremely useful for debug purposes, as it allows you to inspect +the communication between Emacs and an nREPL server. Enabling the logging +might have a negative impact on performance, so it's not recommended to +keep it enabled unless you need to debug something." :type 'boolean :group 'nrepl :safe #'booleanp) @@ -1363,13 +1348,14 @@ The default buffer name is *nrepl-error*." (set-window-point win (point-max))) (setq buffer-read-only t))) -(defun nrepl-create-client-buffer-default (endpoint) +(defun nrepl-default-client-buffer-builder (endpoint) "Create an nREPL client process buffer. ENDPOINT is a plist returned by `nrepl-connect'." (let ((buffer (generate-new-buffer - (nrepl-connection-buffer-name default-directory - (plist-get endpoint :host) - (plist-get endpoint :port))))) + (nrepl-repl-buffer-name + default-directory + (plist-get endpoint :host) + (plist-get endpoint :port))))) (with-current-buffer buffer (buffer-disable-undo) (setq-local kill-buffer-query-functions nil)) diff --git a/test/cider-client-tests.el b/test/cider-client-tests.el index 2231e4ce8..6cba3797a 100644 --- a/test/cider-client-tests.el +++ b/test/cider-client-tests.el @@ -211,26 +211,26 @@ (expect (cider-other-connection bb2) :to-equal b1))))))))) (describe "cider-var-info" - (it "returns vars info as an alist" - (spy-on 'cider-sync-request:info :and-return-value - '(dict - "arglists" "([] [x] [x & ys])" - "ns" "clojure.core" - "name" "str" - "column" 1 - "added" "1.0" - "static" "true" - "doc" "stub" - "line" 504 - "file" "jar:file:/clojure-1.5.1.jar!/clojure/core.clj" - "tag" "class java.lang.String" - "status" ("done"))) - (spy-on 'cider-ensure-op-supported :and-return-value t) - (spy-on 'cider-current-session :and-return-value nil) - (spy-on 'cider-current-ns :and-return-value "user") - (expect (nrepl-dict-get (cider-var-info "str") "doc") - :to-equal "stub") - (expect (cider-var-info "") :to-equal nil))) + (it "returns vars info as an alist" + (spy-on 'cider-sync-request:info :and-return-value + '(dict + "arglists" "([] [x] [x & ys])" + "ns" "clojure.core" + "name" "str" + "column" 1 + "added" "1.0" + "static" "true" + "doc" "stub" + "line" 504 + "file" "jar:file:/clojure-1.5.1.jar!/clojure/core.clj" + "tag" "class java.lang.String" + "status" ("done"))) + (spy-on 'cider-ensure-op-supported :and-return-value t) + (spy-on 'cider-nrepl-eval-session :and-return-value nil) + (spy-on 'cider-current-ns :and-return-value "user") + (expect (nrepl-dict-get (cider-var-info "str") "doc") + :to-equal "stub") + (expect (cider-var-info "") :to-equal nil))) (describe "cider-toggle-buffer-connection" (spy-on 'message :and-return-value nil) @@ -353,7 +353,7 @@ (expect (cider--connection-info (current-buffer)) :to-equal "CLJ @localhost:4005 (Java 1.7, Clojure 1.7.0, nREPL 0.2.1)"))))) -(describe "cider--close-connection-buffer" +(describe "cider--close-connection" :var (connections) (it "removes the connection from `cider-connections'" (setq connections (cider-connections)) diff --git a/test/cider-selector-tests.el b/test/cider-selector-tests.el index 63eee4c91..3d1693fa8 100644 --- a/test/cider-selector-tests.el +++ b/test/cider-selector-tests.el @@ -68,9 +68,9 @@ (cider--test-selector-method ?e 'emacs-lisp-mode "*testfile*.el"))) (describe "cider-seletor-method-r" - :var (cider-current-repl-buffer) + :var (cider-current-connection) (it "switches to current REPL buffer" - (spy-on 'cider-current-repl-buffer :and-return-value "*cider-repl xyz*") + (spy-on 'cider-current-connection :and-return-value "*cider-repl xyz*") (cider--test-selector-method ?r 'cider-repl-mode "*cider-repl xyz*"))) (describe "cider-selector-method-m"