|
|
;;; cider-client.el --- A layer of abstraction above the actual client code. -*- lexical-binding: t -*- |
|
|
|
|
|
;; Copyright © 2013-2015 Bozhidar Batsov |
|
|
;; |
|
|
;; Author: Bozhidar Batsov <bozhidar@batsov.com> |
|
|
|
|
|
;; This program is free software: you can redistribute it and/or modify |
|
|
;; it under the terms of the GNU General Public License as published by |
|
|
;; the Free Software Foundation, either version 3 of the License, or |
|
|
;; (at your option) any later version. |
|
|
|
|
|
;; This program is distributed in the hope that it will be useful, |
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
;; GNU General Public License for more details. |
|
|
|
|
|
;; You should have received a copy of the GNU General Public License |
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
;; This file is not part of GNU Emacs. |
|
|
|
|
|
;;; Commentary: |
|
|
|
|
|
;; A layer of abstraction above the actual client code. |
|
|
|
|
|
;;; Code: |
|
|
|
|
|
(require 'spinner) |
|
|
(require 'nrepl-client) |
|
|
(require 'cider-common) |
|
|
|
|
|
(require 'cider-compat) |
|
|
(require 'seq) |
|
|
|
|
|
;;; Connection Buffer Management |
|
|
|
|
|
(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) |
|
|
(error "No active nREPL connections"))) |
|
|
|
|
|
(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")))) |
|
|
|
|
|
(define-obsolete-function-alias 'nrepl-current-connection-buffer 'cider-default-connection "0.10") |
|
|
|
|
|
(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 `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 `cider-connections'. |
|
|
Also close associated REPL and server buffers." |
|
|
(let ((buffer (get-buffer conn-buffer))) |
|
|
(setq cider-connections |
|
|
(delq buffer cider-connections)) |
|
|
(when (buffer-live-p buffer) |
|
|
(with-current-buffer buffer |
|
|
(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)))) |
|
|
|
|
|
|
|
|
;;; 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 (project-directory &optional all-connections) |
|
|
"Return the most appropriate connection-buffer for the given PROJECT-DIRECTORY. |
|
|
By order of preference, this is any connection whose directory matches |
|
|
PROJECT-DIRECTORY, followed by any connection whose directory is nil, |
|
|
followed by any connection at all. |
|
|
Only return nil if `cider-connections' is empty (there are no connections). |
|
|
|
|
|
If more than one connection satisfy a given level of preference, return the |
|
|
connection buffer closer to the start of `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." |
|
|
(let ((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-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: " nil (clojure-project-dir) nil (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-clear-buffer-local-connection () |
|
|
"Remove association between the current buffer and a connection." |
|
|
(interactive) |
|
|
(cider-ensure-connected) |
|
|
(kill-local-variable 'cider-connections)) |
|
|
|
|
|
(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'). |
|
|
If there is ambiguity, it is resolved by matching TYPE with the REPL |
|
|
type (Clojure or ClojureScript). If TYPE is nil, it is derived from the |
|
|
file extension." |
|
|
;; Cleanup the connections list. |
|
|
(cider-connections) |
|
|
(cond |
|
|
((cider--in-connection-buffer-p) (current-buffer)) |
|
|
((= 1 (length cider-connections)) (car cider-connections)) |
|
|
(t (let* ((project-directory (clojure-project-dir (cider-current-dir))) |
|
|
(repls (and project-directory |
|
|
(cider-find-connection-buffer-for-project-directory project-directory 'all)))) |
|
|
(if (= 1 (length repls)) |
|
|
;; Only one match, just return it. |
|
|
(car repls) |
|
|
;; OW, find one matching the extension of current file. |
|
|
(let ((type (or type (file-name-extension (or (buffer-file-name) ""))))) |
|
|
(or (seq-find (lambda (conn) |
|
|
(equal (with-current-buffer conn |
|
|
(or cider-repl-type "clj")) |
|
|
type)) |
|
|
(append repls cider-connections)) |
|
|
(car repls) |
|
|
(car cider-connections)))))))) |
|
|
|
|
|
|
|
|
;;; 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}" |
|
|
(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))) |
|
|
|
|
|
(define-obsolete-function-alias 'nrepl-connection-browser 'cider-connection-browser "0.10") |
|
|
|
|
|
(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\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--connection-pp (connection) |
|
|
"Print an nREPL CONNECTION to the current buffer." |
|
|
(let* ((buffer-read-only nil) |
|
|
(buffer (get-buffer connection)) |
|
|
(endpoint (buffer-local-value 'nrepl-endpoint buffer))) |
|
|
(insert |
|
|
(format "%s %-30s %-16s %5s %s%s" |
|
|
(if (equal connection (car cider-connections)) "*" " ") |
|
|
(buffer-name connection) |
|
|
(car endpoint) |
|
|
(prin1-to-string (cadr endpoint)) |
|
|
(or (cider--project-name |
|
|
(buffer-local-value 'nrepl-project-dir buffer)) |
|
|
"") |
|
|
(with-current-buffer buffer |
|
|
(if cider-repl-type |
|
|
(concat " " cider-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 "Connected. %s" (cider-random-words-of-inspiration))) |
|
|
|
|
|
;; TODO: Replace direct usage of such hooks with CIDER hooks, |
|
|
;; that are connection type independent |
|
|
(add-hook 'nrepl-connected-hook 'cider-display-connected-message) |
|
|
|
|
|
;;; Evaluation helpers |
|
|
(defun cider-ns-form-p (form) |
|
|
"Check if FORM is an ns form." |
|
|
(string-match-p "^[[:space:]]*\(ns\\([[:space:]]*$\\|[[:space:]]+\\)" form)) |
|
|
|
|
|
(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.") |
|
|
|
|
|
(defun cider-current-ns () |
|
|
"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\"." |
|
|
(or cider-buffer-ns |
|
|
(clojure-find-ns) |
|
|
(when-let ((repl-buf (cider-current-connection))) |
|
|
(buffer-local-value 'cider-buffer-ns repl-buf)) |
|
|
"user")) |
|
|
|
|
|
(define-obsolete-function-alias 'cider-eval 'nrepl-request:eval "0.9") |
|
|
|
|
|
(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))) |
|
|
|
|
|
(defvar cider-version) |
|
|
(defun cider-ensure-op-supported (op) |
|
|
"Check for support of middleware op OP. |
|
|
Signal an error if it is not supported." |
|
|
(unless (cider-nrepl-op-supported-p op) |
|
|
(error "Can't find nREPL middleware providing op \"%s\". Please, install (or update) cider-nrepl %s and restart CIDER" op (upcase cider-version)))) |
|
|
|
|
|
(defun cider-nrepl-send-request (request callback) |
|
|
"Send REQUEST and register response handler CALLBACK. |
|
|
REQUEST is a pair list of the form (\"op\" \"operation\" \"par1-name\" |
|
|
\"par1\" ... ). |
|
|
Return the id of the sent message." |
|
|
(nrepl-send-request request callback (cider-current-connection))) |
|
|
|
|
|
(defun cider-nrepl-send-sync-request (request &optional abort-on-input) |
|
|
"Send REQUEST to the nREPL server synchronously. |
|
|
Hold till final \"done\" message has arrived and join all response messages |
|
|
of the same \"op\" that came along and return the accumulated response. |
|
|
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 (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)) |
|
|
(id (nrepl-send-request request #'ignore conn))) |
|
|
(with-current-buffer conn |
|
|
(nrepl--mark-id-completed id)) |
|
|
id)) |
|
|
|
|
|
(defun cider-nrepl-request:eval (input callback &optional ns point) |
|
|
"Send the request INPUT and register the CALLBACK as the response handler. |
|
|
If NS is non-nil, include it in the request. POINT, if non-nil, is the |
|
|
position of INPUT in its buffer." |
|
|
(nrepl-request:eval input |
|
|
callback |
|
|
(cider-current-connection) |
|
|
(cider-current-session) |
|
|
ns |
|
|
point)) |
|
|
|
|
|
(defun cider-nrepl-sync-request:eval (input &optional ns) |
|
|
"Send the INPUT to the nREPL server synchronously. |
|
|
If NS is non-nil, include it in the request." |
|
|
(nrepl-sync-request:eval |
|
|
input |
|
|
(cider-current-connection) |
|
|
(cider-current-session) |
|
|
ns)) |
|
|
|
|
|
(defun cider--nrepl-pprint-eval-request (input session &optional ns right-margin) |
|
|
"Prepare :pprint-eval request message for INPUT. |
|
|
SESSION and NS are used for the context of the evaluation. |
|
|
RIGHT-MARGIN specifies the maximum column-width of the pretty-printed |
|
|
result, and is included in the request if non-nil." |
|
|
(append (list "pprint" "true") |
|
|
(and right-margin (list "right-margin" right-margin)) |
|
|
(nrepl--eval-request input session ns))) |
|
|
|
|
|
(defun cider-nrepl-request:pprint-eval (input callback &optional ns right-margin) |
|
|
"Send the request INPUT and register the CALLBACK as the response handler. |
|
|
The request is dispatched via CONNECTION and SESSION. |
|
|
If NS is non-nil, include it in the request. |
|
|
RIGHT-MARGIN specifies the maximum column width of the |
|
|
pretty-printed result, and is included in the request if non-nil." |
|
|
(cider-nrepl-send-request |
|
|
(cider--nrepl-pprint-eval-request input (cider-current-session) ns right-margin) |
|
|
callback)) |
|
|
|
|
|
|
|
|
(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." |
|
|
;; namespace forms are always evaluated in the "user" namespace |
|
|
(nrepl-request:eval input |
|
|
callback |
|
|
(cider-current-connection) |
|
|
(cider-current-tooling-session) |
|
|
ns)) |
|
|
|
|
|
(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) |
|
|
(with-current-buffer (cider-current-connection) |
|
|
(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-session)))))) |
|
|
|
|
|
(defun cider-current-session () |
|
|
"The REPL session to use for this buffer." |
|
|
(with-current-buffer (cider-current-connection) |
|
|
nrepl-session)) |
|
|
|
|
|
(define-obsolete-function-alias 'nrepl-current-session 'cider-current-session "0.10") |
|
|
|
|
|
(defun cider-current-tooling-session () |
|
|
"Return the current tooling session." |
|
|
(with-current-buffer (cider-current-connection) |
|
|
nrepl-tooling-session)) |
|
|
|
|
|
(define-obsolete-function-alias 'nrepl-current-tooling-session 'cider-current-tooling-session "0.10") |
|
|
|
|
|
(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 |
|
|
method, and multiple classes have a so-named member. If VAR-INFO does not |
|
|
contain a `candidates' key, it is returned as is." |
|
|
(let ((candidates (nrepl-dict-get var-info "candidates"))) |
|
|
(if candidates |
|
|
(let* ((classes (nrepl-dict-keys candidates)) |
|
|
(choice (completing-read "Member in class: " classes nil t)) |
|
|
(info (nrepl-dict-get candidates choice))) |
|
|
info) |
|
|
var-info))) |
|
|
|
|
|
(defun cider-var-info (var &optional all) |
|
|
"Return VAR's info as an alist with list cdrs. |
|
|
When multiple matching vars are returned you'll be prompted to select one, |
|
|
unless ALL is truthy." |
|
|
(when (and var (not (string= var ""))) |
|
|
(let ((var-info (cider-sync-request:info var))) |
|
|
(if all var-info (cider--var-choice var-info))))) |
|
|
|
|
|
(defun cider-member-info (class member) |
|
|
"Return the CLASS MEMBER's info as an alist with list cdrs." |
|
|
(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") |
|
|
(defun cider-request:load-file (file-contents file-path file-name &optional callback) |
|
|
"Perform the nREPL \"load-file\" op. |
|
|
FILE-CONTENTS, FILE-PATH and FILE-NAME are details of the file to be |
|
|
loaded. If CALLBACK is nil, use `cider-load-file-handler'." |
|
|
(cider-nrepl-send-request (list "op" "load-file" |
|
|
"session" (cider-current-session) |
|
|
"file" file-contents |
|
|
"file-path" file-path |
|
|
"file-name" file-name) |
|
|
(or callback |
|
|
(cider-load-file-handler (current-buffer))))) |
|
|
|
|
|
|
|
|
;;; Sync Requests |
|
|
(defun cider-sync-request:apropos (query &optional search-ns docs-p privates-p case-sensitive-p) |
|
|
"Send \"apropos\" op with args SEARCH-NS, DOCS-P, PRIVATES-P, CASE-SENSITIVE-P." |
|
|
(thread-first `("op" "apropos" |
|
|
"ns" ,(cider-current-ns) |
|
|
"query" ,query |
|
|
,@(when search-ns `("search-ns" ,search-ns)) |
|
|
,@(when docs-p '("docs?" "t")) |
|
|
,@(when privates-p '("privates?" "t")) |
|
|
,@(when case-sensitive-p '("case-sensitive?" "t"))) |
|
|
(cider-nrepl-send-sync-request) |
|
|
(nrepl-dict-get "apropos-matches"))) |
|
|
|
|
|
(defun cider-sync-request:classpath () |
|
|
"Return a list of classpath entries." |
|
|
(cider-ensure-op-supported "classpath") |
|
|
(thread-first (list "op" "classpath" |
|
|
"session" (cider-current-session)) |
|
|
(cider-nrepl-send-sync-request) |
|
|
(nrepl-dict-get "classpath"))) |
|
|
|
|
|
(defun cider-sync-request:complete (str context) |
|
|
"Return a list of completions for STR using nREPL's \"complete\" op." |
|
|
(when-let ((dict (thread-first (list "op" "complete" |
|
|
"session" (cider-current-session) |
|
|
"ns" (cider-current-ns) |
|
|
"symbol" str |
|
|
"context" context) |
|
|
(cider-nrepl-send-sync-request 'abort-on-input)))) |
|
|
(nrepl-dict-get dict "completions"))) |
|
|
|
|
|
(defun cider-sync-request:info (symbol &optional class member) |
|
|
"Send \"info\" op with parameters SYMBOL or CLASS and MEMBER." |
|
|
(let ((var-info (thread-first `("op" "info" |
|
|
"session" ,(cider-current-session) |
|
|
"ns" ,(cider-current-ns) |
|
|
,@(when symbol (list "symbol" symbol)) |
|
|
,@(when class (list "class" class)) |
|
|
,@(when member (list "member" member))) |
|
|
(cider-nrepl-send-sync-request)))) |
|
|
(if (member "no-info" (nrepl-dict-get var-info "status")) |
|
|
nil |
|
|
var-info))) |
|
|
|
|
|
(defun cider-sync-request:eldoc (symbol &optional class member) |
|
|
"Send \"eldoc\" op with parameters SYMBOL or CLASS and MEMBER." |
|
|
(when-let ((eldoc (thread-first `("op" "eldoc" |
|
|
"session" ,(cider-current-session) |
|
|
"ns" ,(cider-current-ns) |
|
|
,@(when symbol (list "symbol" symbol)) |
|
|
,@(when class (list "class" class)) |
|
|
,@(when member (list "member" member))) |
|
|
(cider-nrepl-send-sync-request 'abort-on-input)))) |
|
|
(if (member "no-eldoc" (nrepl-dict-get eldoc "status")) |
|
|
nil |
|
|
eldoc))) |
|
|
|
|
|
(defun cider-sync-request:ns-list () |
|
|
"Get a list of the available namespaces." |
|
|
(thread-first (list "op" "ns-list" |
|
|
"session" (cider-current-session)) |
|
|
(cider-nrepl-send-sync-request) |
|
|
(nrepl-dict-get "ns-list"))) |
|
|
|
|
|
(defun cider-sync-request:ns-vars (ns) |
|
|
"Get a list of the vars in NS." |
|
|
(thread-first (list "op" "ns-vars" |
|
|
"session" (cider-current-session) |
|
|
"ns" ns) |
|
|
(cider-nrepl-send-sync-request) |
|
|
(nrepl-dict-get "ns-vars"))) |
|
|
|
|
|
(defun cider-sync-request:resource (name) |
|
|
"Perform nREPL \"resource\" op with resource name NAME." |
|
|
(thread-first (list "op" "resource" |
|
|
"name" name) |
|
|
(cider-nrepl-send-sync-request) |
|
|
(nrepl-dict-get "resource-path"))) |
|
|
|
|
|
(defun cider-sync-request:resources-list () |
|
|
"Perform nREPL \"resource\" op with resource name NAME." |
|
|
(thread-first (list "op" "resources-list") |
|
|
(cider-nrepl-send-sync-request) |
|
|
(nrepl-dict-get "resources-list"))) |
|
|
|
|
|
(defun cider-sync-request:format-code (code) |
|
|
"Perform nREPL \"format-code\" op with CODE." |
|
|
(thread-first (list "op" "format-code" |
|
|
"code" code) |
|
|
(cider-nrepl-send-sync-request) |
|
|
(nrepl-dict-get "formatted-code"))) |
|
|
|
|
|
(defun cider-sync-request:format-edn (edn &optional right-margin) |
|
|
"Perform \"format-edn\" op with EDN and RIGHT-MARGIN." |
|
|
(let* ((response (thread-first (list "op" "format-edn" |
|
|
"edn" edn) |
|
|
(append (and right-margin (list "right-margin" right-margin))) |
|
|
(cider-nrepl-send-sync-request))) |
|
|
(err (nrepl-dict-get response "err"))) |
|
|
(when err |
|
|
;; err will be a stacktrace with a first line that looks like: |
|
|
;; "clojure.lang.ExceptionInfo: Unmatched delimiter ]" |
|
|
(error (car (split-string err "\n")))) |
|
|
(nrepl-dict-get response "formatted-edn"))) |
|
|
|
|
|
|
|
|
;;; Eval spinner |
|
|
(defcustom cider-eval-spinner-type 'progress-bar |
|
|
"Appearance of the evaluation spinner. |
|
|
|
|
|
Value is a symbol. The possible values are the symbols in the |
|
|
`spinner-types' variable." |
|
|
:type 'symbol |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defcustom cider-show-eval-spinner t |
|
|
"When true, show the evaluation spinner in the mode line." |
|
|
:type 'boolean |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defcustom cider-eval-spinner-delay 1 |
|
|
"Amount of time, in seconds, after which the evaluation spinner will be shown." |
|
|
:type 'integer |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defun cider-spinner-start () |
|
|
"Start the evaluation spinner. |
|
|
Do nothing if `cider-show-eval-spinner' is nil." |
|
|
(when cider-show-eval-spinner |
|
|
(spinner-start cider-eval-spinner-type nil |
|
|
cider-eval-spinner-delay))) |
|
|
|
|
|
(defun cider-eval-spinner-handler (eval-buffer original-callback) |
|
|
"Return a response handler that stops the spinner and calls ORIGINAL-CALLBACK. |
|
|
EVAL-BUFFER is the buffer where the spinner was started." |
|
|
(lambda (response) |
|
|
;; buffer still exists and |
|
|
;; we've got status "done" from nrepl |
|
|
;; stop the spinner |
|
|
(when (and (buffer-live-p eval-buffer) |
|
|
(let ((status (nrepl-dict-get response "status"))) |
|
|
(or (member "done" status) |
|
|
(member "eval-error" status) |
|
|
(member "error" status)))) |
|
|
(with-current-buffer eval-buffer |
|
|
(spinner-stop))) |
|
|
(funcall original-callback response))) |
|
|
|
|
|
|
|
|
;;; 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)" |
|
|
(if cider-repl-type |
|
|
(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 |
|
|
:host (car nrepl-endpoint) |
|
|
:port (cadr nrepl-endpoint) |
|
|
:project-dir nrepl-project-dir))) |
|
|
|
|
|
(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))))) |
|
|
|
|
|
(define-obsolete-function-alias 'cider-display-current-connection-info 'cider-display-connection-info "0.10") |
|
|
|
|
|
(defun cider-rotate-default-connection () |
|
|
"Rotate and display the default nREPL connection." |
|
|
(interactive) |
|
|
(cider-ensure-connected) |
|
|
(setq cider-connections |
|
|
(append (cdr cider-connections) |
|
|
(list (car cider-connections)))) |
|
|
(message "Default nREPL connection: %s" |
|
|
(cider--connection-info (car cider-connections)))) |
|
|
|
|
|
(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))) |
|
|
|
|
|
(define-obsolete-function-alias 'cider-rotate-connection 'cider-rotate-default-connection "0.10") |
|
|
(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 () |
|
|
"Change the designation in cider buffer names. |
|
|
Buffer names changed are cider-repl and nrepl-server." |
|
|
(interactive) |
|
|
(cider-ensure-connected) |
|
|
(let* ((designation (read-string (format "Change CIDER buffer designation from '%s': " |
|
|
(cider-extract-designation-from-current-repl-buffer)))) |
|
|
(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
|
|
|
|