diff options
Diffstat (limited to 'cider-client.el')
-rw-r--r-- | cider-client.el | 906 |
1 files changed, 115 insertions, 791 deletions
diff --git a/cider-client.el b/cider-client.el index df5b1431..1e09bae2 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-connect) - (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-repl))) + (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-repl)))) (defvar cider-version) (defun cider-ensure-op-supported (op) @@ -686,12 +150,10 @@ 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." - (nrepl-send-request request callback (or connection (cider-current-connection)))) +the current connection. Return the id of the sent message." + (nrepl-send-request request callback (or connection (cider-current-repl)))) (defun cider-nrepl-send-sync-request (request &optional connection abort-on-input) "Send REQUEST to the nREPL server synchronously using CONNECTION. @@ -701,14 +163,13 @@ If ABORT-ON-INPUT is non-nil, the function will return nil at the first sign of user input, so as not to hang the interface." (nrepl-send-sync-request request - (or connection (cider-current-connection)) + (or connection (cider-current-repl)) 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-repl))) (id (nrepl-send-request request #'ignore conn))) (with-current-buffer conn (nrepl--mark-id-completed id)) @@ -719,8 +180,8 @@ Return the id of the sent message." If NS is non-nil, include it in the request. LINE and COLUMN, if non-nil, define the position of INPUT in its buffer. ADDITIONAL-PARAMS is a plist to be appended to the request message. CONNECTION is the connection -buffer, defaults to (cider-current-connection)." - (let ((connection (or connection (cider-current-connection)))) +buffer, defaults to (cider-current-repl)." + (let ((connection (or connection (cider-current-repl)))) (nrepl-request:eval input (if cider-show-eval-spinner (cider-eval-spinner-handler connection callback) @@ -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-repl)) 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,70 +243,82 @@ 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-repl)) + 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-repl)) 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) +(defun cider-classpath-libs () + "Return a list of all libs on the classpath." + (let ((libs (seq-filter (lambda (cp-entry) + (string-suffix-p ".jar" cp-entry)) + (cider-sync-request:classpath))) + (dir-sep (if (string-equal system-type "windows-nt") "\\\\" "/"))) + (thread-last libs + (seq-map (lambda (s) (split-string s dir-sep))) + (seq-map #'reverse) + (seq-map (lambda (l) (reverse (seq-take l 4))))))) (defun cider-library-present-p (lib) - "Check whether LIB is present on the classpath." - (seq-find (lambda (s) (string-match-p (concat lib ".*\\.jar") s)) (cider-sync-request:classpath))) + "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)) + (artifact-id (cadr lib))) + (seq-find (lambda (lib) + (let ((g (car lib)) + (a (cadr lib))) + (and (equal group-id g) (equal artifact-id a)))) + (cider-classpath-libs)))) + + +;;; Interrupt evaluation -(defalias 'cider-current-repl-buffer #'cider-current-connection - "The current REPL buffer. -Return the REPL buffer given by `cider-current-connection'.") +(defun cider-interrupt-handler (buffer) + "Create an interrupt response handler for BUFFER." + (nrepl-make-response-handler buffer nil nil nil nil)) -(declare-function cider-interrupt-handler "cider-interaction") (defun cider-interrupt () "Interrupt any pending evaluations." (interactive) - (with-current-buffer (cider-current-connection) + ;; FIXME: does this work correctly in cljc files? + (with-current-buffer (cider-current-repl) (let ((pending-request-ids (cider-util--hash-keys nrepl-pending-requests))) (dolist (request-id pending-request-ids) (nrepl-request:interrupt request-id (cider-interrupt-handler (current-buffer)) - (cider-current-connection)))))) + (cider-current-repl)))))) -(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-repl) 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) + (with-current-buffer (cider-current-repl) nrepl-tooling-session)) (defun cider--var-choice (var-info) @@ -876,53 +347,15 @@ unless ALL is truthy." (when (and class member) (cider-sync-request:info nil class member))) -(defun cider--find-var-other-window (var &optional line) - "Find the definition of VAR, optionally at a specific LINE. - -Display the results in a different window." - (if-let* ((info (cider-var-info var))) - (progn - (if line (setq info (nrepl-dict-put info "line" line))) - (cider--jump-to-loc-from-info info t)) - (user-error "Symbol `%s' not resolved" var))) - -(defun cider--find-var (var &optional line) - "Find the definition of VAR, optionally at a specific LINE." - (if-let* ((info (cider-var-info var))) - (progn - (if line (setq info (nrepl-dict-put info "line" line))) - (cider--jump-to-loc-from-info info)) - (user-error "Symbol `%s' not resolved" var))) - -(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 -the results to be displayed in a different window. The default value is -thing at point." - (interactive "P") - (cider-ensure-op-supported "info") - (if var - (cider--find-var var line) - (funcall (cider-prompt-for-symbol-function arg) - "Symbol" - (if (cider--open-other-window-p arg) - #'cider--find-var-other-window - #'cider--find-var)))) - ;;; Requests -(declare-function cider-load-file-handler "cider-interaction") +(declare-function cider-load-file-handler "cider-eval") (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-repl'. If CALLBACK +is nil, use `cider-load-file-handler'." (cider-nrepl-send-request `("op" "load-file" "file" ,file-contents "file-path" ,file-path @@ -935,14 +368,14 @@ If CALLBACK is nil, use `cider-load-file-handler'." ;;; Sync Requests (defcustom cider-filtered-namespaces-regexps - '("^cider.nrepl" "^refactor-nrepl" "^clojure.tools.nrepl") + '("^cider.nrepl" "^refactor-nrepl" "^clojure.tools.nrepl" "^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 @@ -986,7 +419,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) @@ -1025,7 +458,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 "")) @@ -1091,7 +523,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) @@ -1118,135 +549,28 @@ 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) "<no project>") - (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) "<no designation>"))) - -(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))) +;;; Dealing with input +;; TODO: Replace this with some nil handler. +(defun cider-stdin-handler (&optional _buffer) + "Make a stdin response handler for _BUFFER." + (nrepl-make-response-handler (current-buffer) + (lambda (_buffer _value)) + (lambda (_buffer _out)) + (lambda (_buffer _err)) + nil)) + +(defun cider-need-input (buffer) + "Handle an need-input request from BUFFER." + (with-current-buffer buffer + (let ((map (make-sparse-keymap))) + (set-keymap-parent map minibuffer-local-map) + (define-key map (kbd "C-c C-c") 'abort-recursive-edit) + (let ((stdin (condition-case nil + (concat (read-from-minibuffer "Stdin: " nil map) "\n") + (quit nil)))) + (nrepl-request:stdin stdin + (cider-stdin-handler buffer) + (cider-current-repl)))))) (provide 'cider-client) |