|
|
;;; cider-interaction.el --- IDE for Clojure -*- lexical-binding: t -*- |
|
|
|
|
|
;; Copyright © 2012-2015 Tim King, Phil Hagelberg |
|
|
;; Copyright © 2013-2015 Bozhidar Batsov, Hugo Duncan, Steve Purcell |
|
|
;; |
|
|
;; Author: Tim King <kingtim@gmail.com> |
|
|
;; Phil Hagelberg <technomancy@gmail.com> |
|
|
;; Bozhidar Batsov <bozhidar@batsov.com> |
|
|
;; Hugo Duncan <hugo@hugoduncan.org> |
|
|
;; Steve Purcell <steve@sanityinc.com> |
|
|
|
|
|
;; This program is free software: you can redistribute it and/or modify |
|
|
;; it under the terms of the GNU General Public License as published by |
|
|
;; the Free Software Foundation, either version 3 of the License, or |
|
|
;; (at your option) any later version. |
|
|
|
|
|
;; This program is distributed in the hope that it will be useful, |
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
;; GNU General Public License for more details. |
|
|
|
|
|
;; You should have received a copy of the GNU General Public License |
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
;; This file is not part of GNU Emacs. |
|
|
|
|
|
;;; Commentary: |
|
|
|
|
|
;; Provides an Emacs Lisp client to connect to Clojure nREPL servers. |
|
|
|
|
|
;;; Code: |
|
|
|
|
|
(require 'cider-client) |
|
|
(require 'cider-repl) |
|
|
(require 'cider-popup) |
|
|
(require 'cider-common) |
|
|
(require 'cider-util) |
|
|
(require 'cider-stacktrace) |
|
|
(require 'cider-test) |
|
|
(require 'cider-doc) |
|
|
(require 'cider-eldoc) |
|
|
(require 'cider-overlays) |
|
|
(require 'cider-compat) |
|
|
|
|
|
(require 'clojure-mode) |
|
|
(require 'thingatpt) |
|
|
(require 'etags) |
|
|
(require 'arc-mode) |
|
|
(require 'ansi-color) |
|
|
(require 'cl-lib) |
|
|
(require 'compile) |
|
|
(require 'tramp) |
|
|
|
|
|
(defconst cider-read-eval-buffer "*cider-read-eval*") |
|
|
(defconst cider-result-buffer "*cider-result*") |
|
|
(defconst cider-nrepl-session-buffer "*cider-nrepl-session*") |
|
|
(add-to-list 'cider-ancillary-buffers cider-nrepl-session-buffer) |
|
|
|
|
|
(defcustom cider-show-error-buffer t |
|
|
"Control the popup behavior of cider stacktraces. |
|
|
The following values are possible t or 'always, 'except-in-repl, |
|
|
'only-in-repl. Any other value, including nil, will cause the stacktrace |
|
|
not to be automatically shown. |
|
|
|
|
|
Irespective of the value of this variable, the `cider-error-buffer' is |
|
|
always generated in the background. Use `cider-visit-error-buffer' to |
|
|
navigate to this buffer." |
|
|
:type '(choice (const :tag "always" t) |
|
|
(const except-in-repl) |
|
|
(const only-in-repl) |
|
|
(const :tag "never" nil)) |
|
|
:group 'cider) |
|
|
|
|
|
(defcustom cider-auto-jump-to-error t |
|
|
"When non-nil automatically jump to error location during interactive compilation. |
|
|
When set to 'errors-only, don't jump to warnings." |
|
|
:type '(choice (const :tag "always" t) |
|
|
(const errors-only) |
|
|
(const :tag "never" nil)) |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.7.0")) |
|
|
|
|
|
(defcustom cider-auto-select-error-buffer t |
|
|
"Controls whether to auto-select the error popup buffer." |
|
|
:type 'boolean |
|
|
:group 'cider) |
|
|
|
|
|
(defcustom cider-prompt-save-file-on-load t |
|
|
"Controls whether to prompt to save the file when loading a buffer. |
|
|
If nil, files are not saved. |
|
|
If t, the user is prompted to save the file if it's been modified. |
|
|
If the symbol `always-save', save the file without confirmation." |
|
|
:type '(choice (const t :tag "Prompt to save the file if it's been modified") |
|
|
(const nil :tag "Don't save the file") |
|
|
(const always-save :tag "Save the file without confirmation")) |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.6.0")) |
|
|
|
|
|
(defcustom cider-completion-use-context t |
|
|
"When true, uses context at point to improve completion suggestions." |
|
|
:type 'boolean |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.7.0")) |
|
|
|
|
|
(defcustom cider-annotate-completion-candidates t |
|
|
"When true, annotate completion candidates with some extra information." |
|
|
:type 'boolean |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.8.0")) |
|
|
|
|
|
(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." |
|
|
:type 'function |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.9.0")) |
|
|
|
|
|
(defcustom cider-completion-annotations-alist |
|
|
'(("class" "c") |
|
|
("field" "fi") |
|
|
("function" "f") |
|
|
("import" "i") |
|
|
("keyword" "k") |
|
|
("local" "l") |
|
|
("macro" "m") |
|
|
("method" "me") |
|
|
("namespace" "n") |
|
|
("protocol" "p") |
|
|
("protocol-function" "pf") |
|
|
("record" "r") |
|
|
("special-form" "s") |
|
|
("static-field" "sf") |
|
|
("static-method" "sm") |
|
|
("type" "t") |
|
|
("var" "v")) |
|
|
"Controls the abbreviations used when annotating completion candidates. |
|
|
|
|
|
Must be a list of elements with the form (TYPE . ABBREVIATION), where TYPE |
|
|
is a possible value of the candidate's type returned from the completion |
|
|
backend, and ABBREVIATION is a short form of that type." |
|
|
:type '(alist :key-type string :value-type string) |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.9.0")) |
|
|
|
|
|
(defcustom cider-completion-annotations-include-ns 'unqualified |
|
|
"Controls passing of namespaces to `cider-annotate-completion-function'. |
|
|
|
|
|
When set to 'always, the candidate's namespace will always be passed if it |
|
|
is available. When set to 'unqualified, the namespace will only be passed |
|
|
if the candidate is not namespace-qualified." |
|
|
:type '(choice (const always) |
|
|
(const unqualified) |
|
|
(const :tag "never" nil)) |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.9.0")) |
|
|
|
|
|
(defconst cider-refresh-log-buffer "*cider-refresh-log*") |
|
|
|
|
|
(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 |
|
|
displayed automatically. Instead, the most relevant information will be |
|
|
displayed in the echo area." |
|
|
:type '(choice (const :tag "always" t) |
|
|
(const :tag "never" nil)) |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(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." |
|
|
:type 'string |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(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 |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defconst cider-output-buffer "*cider-out*") |
|
|
|
|
|
(defcustom cider-interactive-eval-output-destination 'repl-buffer |
|
|
"The destination for stdout and stderr produced from interactive evaluation." |
|
|
:type '(choice (const output-buffer) |
|
|
(const repl-buffer)) |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.7.0")) |
|
|
|
|
|
(defface cider-error-highlight-face |
|
|
'((((supports :underline (:style wave))) |
|
|
(:underline (:style wave :color "red") :inherit unspecified)) |
|
|
(t (:inherit font-lock-warning-face :underline t))) |
|
|
"Face used to highlight compilation errors in Clojure buffers." |
|
|
:group 'cider) |
|
|
|
|
|
(defface cider-warning-highlight-face |
|
|
'((((supports :underline (:style wave))) |
|
|
(:underline (:style wave :color "yellow") :inherit unspecified)) |
|
|
(t (:inherit font-lock-warning-face :underline (:color "yellow")))) |
|
|
"Face used to highlight compilation warnings in Clojure buffers." |
|
|
:group 'cider) |
|
|
|
|
|
(defvar cider-required-nrepl-ops |
|
|
'("apropos" "classpath" "complete" "eldoc" "format-code" "format-edn" "info" |
|
|
"inspect-pop" "inspect-push" "inspect-refresh" |
|
|
"macroexpand" "ns-list" "ns-vars" "ns-path" "refresh" |
|
|
"resource" "stacktrace" "toggle-trace-var" "toggle-trace-ns" "undef") |
|
|
"A list of nREPL ops required by CIDER to function properly. |
|
|
|
|
|
All of them are provided by CIDER's nREPL middleware (cider-nrepl).") |
|
|
|
|
|
(defvar cider-required-nrepl-version "0.2.7" |
|
|
"The minimum nREPL version that's known to work properly with CIDER.") |
|
|
|
|
|
;;; Minibuffer |
|
|
(defvar cider-minibuffer-history '() |
|
|
"History list of expressions read from the minibuffer.") |
|
|
|
|
|
(defvar cider-minibuffer-map |
|
|
(let ((map (make-sparse-keymap))) |
|
|
(set-keymap-parent map minibuffer-local-map) |
|
|
(define-key map (kbd "TAB") #'complete-symbol) |
|
|
(define-key map (kbd "M-TAB") #'complete-symbol) |
|
|
map) |
|
|
"Minibuffer keymap used for reading Clojure expressions.") |
|
|
|
|
|
(defun cider-read-from-minibuffer (prompt &optional initial-value) |
|
|
"Read a string from the minibuffer, prompting with PROMPT. |
|
|
If INITIAL-VALUE is non-nil, it is inserted into the minibuffer before |
|
|
reading input. |
|
|
PROMPT need not end with \": \"." |
|
|
(minibuffer-with-setup-hook |
|
|
(lambda () |
|
|
(set-syntax-table clojure-mode-syntax-table) |
|
|
(add-hook 'completion-at-point-functions |
|
|
#'cider-complete-at-point nil t) |
|
|
(setq-local eldoc-documentation-function #'cider-eldoc) |
|
|
(run-hooks 'eval-expression-minibuffer-setup-hook)) |
|
|
(read-from-minibuffer (if (string-match ": \\'" prompt) prompt (concat prompt ": ")) |
|
|
initial-value |
|
|
cider-minibuffer-map nil |
|
|
'cider-minibuffer-history))) |
|
|
|
|
|
|
|
|
;;; Utilities |
|
|
|
|
|
(defun cider--clear-compilation-highlights () |
|
|
"Remove compilation highlights." |
|
|
(remove-overlays (point-min) (point-max) 'cider-note-p t)) |
|
|
|
|
|
(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? ")) |
|
|
(cider--clear-compilation-highlights))) |
|
|
|
|
|
(defun cider--quit-error-window () |
|
|
"Buries the `cider-error-buffer' and quits its containing window." |
|
|
(when-let ((error-win (get-buffer-window cider-error-buffer))) |
|
|
(quit-window nil error-win))) |
|
|
|
|
|
;;; |
|
|
(declare-function cider-mode "cider-mode") |
|
|
|
|
|
(defun cider-jump-to (buffer &optional pos other-window) |
|
|
"Push current point onto marker ring, and jump to BUFFER and POS. |
|
|
POS can be either a number, a cons, or a symbol. |
|
|
If a number, it is the character position (the point). |
|
|
If a cons, it specifies the position as (LINE . COLUMN). COLUMN can be nil. |
|
|
If a symbol, `cider-jump-to' searches for something that looks like the |
|
|
symbol's definition in the file. |
|
|
If OTHER-WINDOW is non-nil don't reuse current window." |
|
|
(with-no-warnings |
|
|
(ring-insert find-tag-marker-ring (point-marker))) |
|
|
(if other-window |
|
|
(pop-to-buffer buffer) |
|
|
;; like switch-to-buffer, but reuse existing window if BUFFER is visible |
|
|
(pop-to-buffer buffer '((display-buffer-reuse-window display-buffer-same-window)))) |
|
|
(with-current-buffer buffer |
|
|
(widen) |
|
|
(goto-char (point-min)) |
|
|
(cider-mode +1) |
|
|
(cond |
|
|
;; Line-column specification. |
|
|
((consp pos) |
|
|
(forward-line (1- (or (car pos) 1))) |
|
|
(if (cdr pos) |
|
|
(move-to-column (cdr pos)) |
|
|
(back-to-indentation))) |
|
|
;; Point specification. |
|
|
((numberp pos) |
|
|
(goto-char pos)) |
|
|
;; Symbol or string. |
|
|
(pos |
|
|
;; Try to find (def full-name ...). |
|
|
(if (or (save-excursion |
|
|
(search-forward-regexp (format "(def.*\\s-\\(%s\\)" (regexp-quote pos)) |
|
|
nil 'noerror)) |
|
|
(let ((name (replace-regexp-in-string ".*/" "" pos))) |
|
|
;; Try to find (def name ...). |
|
|
(or (save-excursion |
|
|
(search-forward-regexp (format "(def.*\\s-\\(%s\\)" (regexp-quote name)) |
|
|
nil 'noerror)) |
|
|
;; Last resort, just find the first occurrence of `name'. |
|
|
(save-excursion |
|
|
(search-forward name nil 'noerror))))) |
|
|
(goto-char (match-beginning 0)) |
|
|
(message "Can't find %s in %s" pos (buffer-file-name)))) |
|
|
(t nil)))) |
|
|
|
|
|
(defun cider-find-dwim-other-window (symbol-file) |
|
|
"Jump to SYMBOL-FILE at point, place results in other window." |
|
|
(interactive (cider--find-dwim-interactive "Jump to: ")) |
|
|
(cider--find-dwim symbol-file 'cider-find-dwim-other-window t)) |
|
|
|
|
|
(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." |
|
|
(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))) |
|
|
(cider--jump-to-loc-from-info info other-window) |
|
|
(progn |
|
|
(cider-ensure-op-supported "resource") |
|
|
(if-let ((resource (cider-sync-request:resource symbol-file)) |
|
|
(buffer (cider-find-file resource))) |
|
|
(cider-jump-to buffer 0 other-window) |
|
|
(if (cider--prompt-for-symbol-p current-prefix-arg) |
|
|
(error "Resource or var %s not resolved" symbol-file) |
|
|
(let ((current-prefix-arg (if current-prefix-arg nil '(4)))) |
|
|
(call-interactively callback))))))) |
|
|
|
|
|
(defun cider--find-dwim-interactive (prompt) |
|
|
"Get interactive arguments for jump-to functions using PROMPT as needed." |
|
|
(if (cider--prompt-for-symbol-p current-prefix-arg) |
|
|
(list |
|
|
(cider-read-from-minibuffer prompt (thing-at-point 'filename))) |
|
|
(list (or (thing-at-point 'filename) "")))) ; No prompt. |
|
|
|
|
|
(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 |
|
|
argument causes the results to be displayed in other window. The default |
|
|
value is thing at point." |
|
|
(interactive |
|
|
(list |
|
|
(if (cider--prompt-for-symbol-p current-prefix-arg) |
|
|
(completing-read "Resource: " |
|
|
(cider-sync-request:resources-list) |
|
|
nil nil |
|
|
(thing-at-point 'filename)) |
|
|
(or (thing-at-point 'filename) "")))) |
|
|
(cider-ensure-op-supported "resource") |
|
|
(when (= (length path) 0) |
|
|
(error "Cannot find resource for empty path")) |
|
|
(if-let ((resource (cider-sync-request:resource path)) |
|
|
(buffer (cider-find-file resource))) |
|
|
(cider-jump-to buffer nil (cider--open-other-window-p current-prefix-arg)) |
|
|
(if (cider--prompt-for-symbol-p current-prefix-arg) |
|
|
(error "Cannot find resource %s" path) |
|
|
(let ((current-prefix-arg (cider--invert-prefix-arg current-prefix-arg))) |
|
|
(call-interactively `cider-find-resource))))) |
|
|
|
|
|
(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 |
|
|
(16 -1) ; empty empty -> - |
|
|
(-1 16) ; - -> empty empty |
|
|
(4 nil) ; empty -> no-prefix |
|
|
(_ 4)))) ; no-prefix -> empty |
|
|
|
|
|
(defun cider--prefix-invert-prompt-p (arg) |
|
|
"Test prefix value ARG for its effect on `cider-prompt-for-symbol`." |
|
|
(let ((narg (prefix-numeric-value arg))) |
|
|
(pcase narg |
|
|
(16 t) ; empty empty |
|
|
(4 t) ; empty |
|
|
(_ nil)))) |
|
|
|
|
|
(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) |
|
|
(not cider-prompt-for-symbol) cider-prompt-for-symbol)) |
|
|
|
|
|
(defun cider-sync-request:ns-path (ns) |
|
|
"Get the path to the file containing NS." |
|
|
(thread-first (list "op" "ns-path" |
|
|
"ns" ns) |
|
|
cider-nrepl-send-sync-request |
|
|
(nrepl-dict-get "path"))) |
|
|
|
|
|
(defun cider--find-ns (ns &optional other-window) |
|
|
(if-let ((path (cider-sync-request:ns-path ns))) |
|
|
(cider-jump-to (cider-find-file path) nil other-window) |
|
|
(user-error "Can't find %s" ns))) |
|
|
|
|
|
(defun cider-find-ns (&optional arg ns) |
|
|
"Find the file containing NS. |
|
|
|
|
|
A prefix of `-` or a double prefix argument causes |
|
|
the results to be displayed in a different window." |
|
|
(interactive "P") |
|
|
(cider-ensure-op-supported "ns-path") |
|
|
(if ns |
|
|
(cider--find-ns ns) |
|
|
(let* ((namespaces (cider-sync-request:ns-list)) |
|
|
(ns (completing-read "Find namespace: " namespaces))) |
|
|
(cider--find-ns ns (cider--open-other-window-p arg))))) |
|
|
|
|
|
(define-obsolete-function-alias 'cider-jump-to-resource 'cider-find-resource "0.9.0") |
|
|
(define-obsolete-function-alias 'cider-jump-to-var 'cider-find-var "0.9.0") |
|
|
|
|
|
(defvar cider-completion-last-context nil) |
|
|
|
|
|
(defun cider-completion-symbol-start-pos () |
|
|
"Find the starting position of the symbol at point, unless inside a string." |
|
|
(let ((sap (symbol-at-point))) |
|
|
(when (and sap (not (nth 3 (syntax-ppss)))) |
|
|
(car (bounds-of-thing-at-point 'symbol))))) |
|
|
|
|
|
(defun cider-completion-get-context-at-point () |
|
|
"Extract the context at point. |
|
|
If point is not inside the list, returns nil; otherwise return top-level |
|
|
form, with symbol at point replaced by __prefix__." |
|
|
(when (save-excursion |
|
|
(condition-case _ |
|
|
(progn |
|
|
(up-list) |
|
|
(check-parens) |
|
|
t) |
|
|
(scan-error nil) |
|
|
(user-error nil))) |
|
|
(save-excursion |
|
|
(let* ((pref-end (point)) |
|
|
(pref-start (cider-completion-symbol-start-pos)) |
|
|
(context (cider-defun-at-point)) |
|
|
(_ (beginning-of-defun)) |
|
|
(expr-start (point))) |
|
|
(concat (when pref-start (substring context 0 (- pref-start expr-start))) |
|
|
"__prefix__" |
|
|
(substring context (- pref-end expr-start))))))) |
|
|
|
|
|
(defun cider-completion-get-context () |
|
|
"Extract context depending on `cider-completion-use-context' and major mode." |
|
|
(let ((context (if (and cider-completion-use-context |
|
|
;; Important because `beginning-of-defun' and |
|
|
;; `ending-of-defun' work incorrectly in the REPL |
|
|
;; buffer, so context extraction fails there. |
|
|
(derived-mode-p 'clojure-mode)) |
|
|
(or (cider-completion-get-context-at-point) |
|
|
"nil") |
|
|
"nil"))) |
|
|
(if (string= cider-completion-last-context context) |
|
|
":same" |
|
|
(setq cider-completion-last-context context) |
|
|
context))) |
|
|
|
|
|
(defun cider-completion--parse-candidate-map (candidate-map) |
|
|
(let ((candidate (nrepl-dict-get candidate-map "candidate")) |
|
|
(type (nrepl-dict-get candidate-map "type")) |
|
|
(ns (nrepl-dict-get candidate-map "ns"))) |
|
|
(put-text-property 0 1 'type type candidate) |
|
|
(put-text-property 0 1 'ns ns candidate) |
|
|
candidate)) |
|
|
|
|
|
(defun cider-complete (str) |
|
|
"Complete STR with context at point." |
|
|
(let* ((context (cider-completion-get-context)) |
|
|
(candidates (cider-sync-request:complete str context))) |
|
|
(mapcar #'cider-completion--parse-candidate-map candidates))) |
|
|
|
|
|
(defun cider-completion--get-candidate-type (symbol) |
|
|
(let ((type (get-text-property 0 'type symbol))) |
|
|
(or (cadr (assoc type cider-completion-annotations-alist)) |
|
|
type))) |
|
|
|
|
|
(defun cider-completion--get-candidate-ns (symbol) |
|
|
(when (or (eq 'always cider-completion-annotations-include-ns) |
|
|
(and (eq 'unqualified cider-completion-annotations-include-ns) |
|
|
(not (cider-namespace-qualified-p symbol)))) |
|
|
(get-text-property 0 'ns symbol))) |
|
|
|
|
|
(defun cider-default-annotate-completion-function (type ns) |
|
|
(concat (when ns (format " (%s)" ns)) |
|
|
(when type (format " <%s>" type)))) |
|
|
|
|
|
(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'." |
|
|
(when cider-annotate-completion-candidates |
|
|
(let* ((type (cider-completion--get-candidate-type symbol)) |
|
|
(ns (cider-completion--get-candidate-ns symbol))) |
|
|
(funcall cider-annotate-completion-function type ns)))) |
|
|
|
|
|
(defun cider-complete-at-point () |
|
|
"Complete the symbol at point." |
|
|
(when-let ((sap (cider-symbol-at-point))) |
|
|
(when (and (cider-connected-p) |
|
|
(not (or (cider-in-string-p) (cider-in-comment-p)))) |
|
|
(let ((bounds (bounds-of-thing-at-point 'symbol))) |
|
|
(list (car bounds) (cdr bounds) |
|
|
(completion-table-dynamic #'cider-complete) |
|
|
:annotation-function #'cider-annotate-symbol |
|
|
:company-doc-buffer #'cider-create-doc-buffer |
|
|
:company-location #'cider-company-location |
|
|
:company-docsig #'cider-company-docsig))))) |
|
|
|
|
|
(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)) |
|
|
(file (nrepl-dict-get info "file")) |
|
|
(line (nrepl-dict-get info "line")) |
|
|
(buffer (cider-find-file file))) |
|
|
(with-current-buffer buffer |
|
|
(save-excursion |
|
|
(goto-char (point-min)) |
|
|
(forward-line (1- line)) |
|
|
(cons buffer (point)))))) |
|
|
|
|
|
(defun cider-company-docsig (thing) |
|
|
"Return signature for THING." |
|
|
(let ((arglist (cider-eldoc-arglist thing))) |
|
|
(when arglist |
|
|
(format "%s: %s" |
|
|
(cider-eldoc-format-thing thing) |
|
|
(cider-eldoc-format-arglist arglist 0))))) |
|
|
|
|
|
(defun cider-stdin-handler (&optional buffer) |
|
|
"Make a stdin response handler for BUFFER." |
|
|
(nrepl-make-response-handler (or buffer (current-buffer)) |
|
|
(lambda (buffer value) |
|
|
(cider-repl-emit-result buffer value t)) |
|
|
(lambda (buffer out) |
|
|
(cider-repl-emit-stdout buffer out)) |
|
|
(lambda (buffer err) |
|
|
(cider-repl-emit-stderr buffer err)) |
|
|
nil)) |
|
|
|
|
|
(defun cider-insert-eval-handler (&optional buffer) |
|
|
"Make a nREPL evaluation handler for the BUFFER. |
|
|
The handler simply inserts the result value in BUFFER." |
|
|
(let ((eval-buffer (current-buffer))) |
|
|
(nrepl-make-response-handler (or buffer eval-buffer) |
|
|
(lambda (_buffer value) |
|
|
(with-current-buffer buffer |
|
|
(insert value))) |
|
|
(lambda (_buffer out) |
|
|
(cider-repl-emit-interactive-stdout out)) |
|
|
(lambda (_buffer err) |
|
|
(cider-handle-compilation-errors err eval-buffer)) |
|
|
'()))) |
|
|
|
|
|
(defun cider--emit-interactive-eval-output (output repl-emit-function) |
|
|
"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'." |
|
|
(pcase cider-interactive-eval-output-destination |
|
|
(`output-buffer (let ((output-buffer (or (get-buffer cider-output-buffer) |
|
|
(cider-popup-buffer cider-output-buffer t)))) |
|
|
(cider-emit-into-popup-buffer output-buffer output) |
|
|
(pop-to-buffer output-buffer))) |
|
|
(`repl-buffer (funcall repl-emit-function output)) |
|
|
(_ (error "Unsupported value %s for `cider-interactive-eval-output-destination'" |
|
|
cider-interactive-eval-output-destination)))) |
|
|
|
|
|
(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'." |
|
|
(cider--emit-interactive-eval-output output 'cider-repl-emit-interactive-stdout)) |
|
|
|
|
|
(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'." |
|
|
(cider--emit-interactive-eval-output output 'cider-repl-emit-interactive-stderr)) |
|
|
|
|
|
(defun cider-interactive-eval-handler (&optional buffer point) |
|
|
"Make an interactive eval handler for BUFFER. |
|
|
If POINT is non-nil, it is the position where the evaluated sexp ends. It |
|
|
can be used to display the evaluation result." |
|
|
(let ((eval-buffer (current-buffer)) |
|
|
(point (if point (copy-marker point) (point-marker)))) |
|
|
(nrepl-make-response-handler (or buffer eval-buffer) |
|
|
(lambda (_buffer value) |
|
|
(cider--display-interactive-eval-result value point)) |
|
|
(lambda (_buffer out) |
|
|
(cider-emit-interactive-eval-output out)) |
|
|
(lambda (_buffer err) |
|
|
(cider-emit-interactive-eval-err-output err) |
|
|
(cider-handle-compilation-errors err eval-buffer)) |
|
|
'()))) |
|
|
|
|
|
(defun cider-load-file-handler (&optional buffer) |
|
|
"Make a load file handler for BUFFER." |
|
|
(let ((eval-buffer (current-buffer))) |
|
|
(nrepl-make-response-handler (or buffer eval-buffer) |
|
|
(lambda (buffer value) |
|
|
(cider--display-interactive-eval-result value) |
|
|
(with-current-buffer buffer |
|
|
(run-hooks 'cider-file-loaded-hook))) |
|
|
(lambda (_buffer value) |
|
|
(cider-emit-interactive-eval-output value)) |
|
|
(lambda (_buffer err) |
|
|
(cider-emit-interactive-eval-err-output err) |
|
|
(cider-handle-compilation-errors err eval-buffer)) |
|
|
'() |
|
|
(lambda () |
|
|
(funcall nrepl-err-handler))))) |
|
|
|
|
|
(defun cider-eval-print-handler (&optional buffer) |
|
|
"Make a handler for evaluating and printing result in BUFFER." |
|
|
(nrepl-make-response-handler (or buffer (current-buffer)) |
|
|
(lambda (buffer value) |
|
|
(with-current-buffer buffer |
|
|
(insert |
|
|
(if (derived-mode-p 'cider-clojure-interaction-mode) |
|
|
(format "\n%s\n" value) |
|
|
value)))) |
|
|
(lambda (_buffer out) |
|
|
(cider-emit-interactive-eval-output out)) |
|
|
(lambda (_buffer err) |
|
|
(cider-emit-interactive-eval-err-output err)) |
|
|
'())) |
|
|
|
|
|
(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." |
|
|
(nrepl-make-response-handler (or buffer (current-buffer)) |
|
|
'() |
|
|
(lambda (buffer str) |
|
|
(cider-emit-into-popup-buffer buffer str)) |
|
|
(lambda (buffer str) |
|
|
(cider-emit-into-popup-buffer buffer str)) |
|
|
'())) |
|
|
|
|
|
(defun cider-visit-error-buffer () |
|
|
"Visit the `cider-error-buffer' (usually *cider-error*) if it exists." |
|
|
(interactive) |
|
|
(if-let ((buffer (get-buffer cider-error-buffer))) |
|
|
(cider-popup-buffer-display buffer cider-auto-select-error-buffer) |
|
|
(user-error "No %s buffer" cider-error-buffer))) |
|
|
|
|
|
(defun cider-find-property (property &optional backward) |
|
|
"Find the next text region which has the specified PROPERTY. |
|
|
If BACKWARD is t, then search backward. |
|
|
Returns the position at which PROPERTY was found, or nil if not found." |
|
|
(let ((p (if backward |
|
|
(previous-single-char-property-change (point) property) |
|
|
(next-single-char-property-change (point) property)))) |
|
|
(when (and (not (= p (point-min))) (not (= p (point-max)))) |
|
|
p))) |
|
|
|
|
|
(defun cider-jump-to-compilation-error (&optional _arg _reset) |
|
|
"Jump to the line causing the current compilation error. |
|
|
|
|
|
_ARG and _RESET are ignored, as there is only ever one compilation error. |
|
|
They exist for compatibility with `next-error'." |
|
|
(interactive) |
|
|
(cl-labels ((goto-next-note-boundary |
|
|
() |
|
|
(let ((p (or (cider-find-property 'cider-note-p) |
|
|
(cider-find-property 'cider-note-p t)))) |
|
|
(when p |
|
|
(goto-char p) |
|
|
(message "%s" (get-char-property p 'cider-note)))))) |
|
|
;; if we're already on a compilation error, first jump to the end of |
|
|
;; it, so that we find the next error. |
|
|
(when (get-char-property (point) 'cider-note-p) |
|
|
(goto-next-note-boundary)) |
|
|
(goto-next-note-boundary))) |
|
|
|
|
|
(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))) |
|
|
(replp (with-current-buffer selected-buffer (derived-mode-p 'cider-repl-mode)))) |
|
|
(memq cider-show-error-buffer |
|
|
(if replp |
|
|
'(t always only-in-repl) |
|
|
'(t always except-in-repl))))) |
|
|
|
|
|
(defun cider-new-error-buffer (&optional mode) |
|
|
"Return an empty error buffer using MODE. |
|
|
|
|
|
When deciding whether to display the buffer, takes into account both the |
|
|
value of `cider-show-error-buffer' and the currently selected buffer. |
|
|
|
|
|
When deciding whether to select the buffer, takes into account the value of |
|
|
`cider-auto-select-error-buffer'." |
|
|
(if (cider--show-error-buffer-p) |
|
|
(cider-popup-buffer cider-error-buffer cider-auto-select-error-buffer mode) |
|
|
(cider-make-popup-buffer cider-error-buffer mode))) |
|
|
|
|
|
(defun cider--handle-err-eval-response (response) |
|
|
"Render eval RESPONSE into a new error buffer. |
|
|
|
|
|
Uses the value of the `out' slot in RESPONSE." |
|
|
(nrepl-dbind-response response (out) |
|
|
(when out |
|
|
(let ((error-buffer (cider-new-error-buffer))) |
|
|
(cider-emit-into-color-buffer error-buffer out) |
|
|
(with-current-buffer error-buffer |
|
|
(compilation-minor-mode +1)))))) |
|
|
|
|
|
(defun cider-default-err-eval-handler () |
|
|
"Display the last exception without middleware support." |
|
|
(cider--handle-err-eval-response |
|
|
(cider-nrepl-sync-request:eval |
|
|
"(clojure.stacktrace/print-cause-trace *e)"))) |
|
|
|
|
|
(defun cider--render-stacktrace-causes (causes) |
|
|
"If CAUSES is non-nil, render its contents into a new error buffer." |
|
|
(when causes |
|
|
(let ((error-buffer (cider-new-error-buffer #'cider-stacktrace-mode))) |
|
|
(cider-stacktrace-render error-buffer (reverse causes))))) |
|
|
|
|
|
(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." |
|
|
(nrepl-dbind-response response (class status) |
|
|
(cond (class (cons response causes)) |
|
|
(status (cider--render-stacktrace-causes causes))))) |
|
|
|
|
|
(defun cider-default-err-op-handler () |
|
|
"Display the last exception, with middleware support." |
|
|
;; Causes are returned as a series of messages, which we aggregate in `causes' |
|
|
(let (causes) |
|
|
(cider-nrepl-send-request |
|
|
(append |
|
|
(list "op" "stacktrace" "session" (cider-current-session)) |
|
|
(when cider-stacktrace-print-length |
|
|
(list "print-length" cider-stacktrace-print-length)) |
|
|
(when cider-stacktrace-print-level |
|
|
(list "print-level" cider-stacktrace-print-level))) |
|
|
(lambda (response) |
|
|
;; While the return value of `cider--handle-stacktrace-response' is not |
|
|
;; meaningful for the last message, we do not need the value of `causes' |
|
|
;; after it has been handled, so it's fine to set it unconditionally here |
|
|
(setq causes (cider--handle-stacktrace-response response causes)))))) |
|
|
|
|
|
(defun cider-default-err-handler () |
|
|
"This function determines how the error buffer is shown, and then delegates |
|
|
the actual error content to the eval or op handler." |
|
|
(if (cider-nrepl-op-supported-p "stacktrace") |
|
|
(cider-default-err-op-handler) |
|
|
(cider-default-err-eval-handler))) |
|
|
|
|
|
(defvar cider-compilation-regexp |
|
|
'("\\(?:.*\\(warning, \\)\\|.*?\\(, compiling\\):(\\)\\([^:]*\\):\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?\\(\\(?: - \\(.*\\)\\)\\|)\\)" 3 4 5 (1)) |
|
|
"Specifications for matching errors and warnings in Clojure stacktraces. |
|
|
See `compilation-error-regexp-alist' for help on their format.") |
|
|
|
|
|
(add-to-list 'compilation-error-regexp-alist-alist |
|
|
(cons 'cider cider-compilation-regexp)) |
|
|
(add-to-list 'compilation-error-regexp-alist 'cider) |
|
|
|
|
|
(defun cider-extract-error-info (regexp message) |
|
|
"Extract error information with REGEXP against MESSAGE." |
|
|
(let ((file (nth 1 regexp)) |
|
|
(line (nth 2 regexp)) |
|
|
(col (nth 3 regexp)) |
|
|
(type (nth 4 regexp)) |
|
|
(pat (car regexp))) |
|
|
(when (string-match pat message) |
|
|
;; special processing for type (1.2) style |
|
|
(setq type (if (consp type) |
|
|
(or (and (car type) (match-end (car type)) 1) |
|
|
(and (cdr type) (match-end (cdr type)) 0) |
|
|
2))) |
|
|
(list |
|
|
(when file |
|
|
(let ((val (match-string-no-properties file message))) |
|
|
(unless (string= val "NO_SOURCE_PATH") val))) |
|
|
(when line (string-to-number (match-string-no-properties line message))) |
|
|
(when col |
|
|
(let ((val (match-string-no-properties col message))) |
|
|
(when val (string-to-number val)))) |
|
|
(aref [cider-warning-highlight-face |
|
|
cider-warning-highlight-face |
|
|
cider-error-highlight-face] |
|
|
(or type 2)) |
|
|
message)))) |
|
|
|
|
|
(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)) |
|
|
(null (nth 3 (syntax-ppss)))) |
|
|
(backward-sexp) |
|
|
(while (or (not (looking-at-p "[({[]")) |
|
|
(nth 3 (syntax-ppss))) |
|
|
(backward-char)))) |
|
|
|
|
|
(defun cider--find-last-error-location (message) |
|
|
"Return the location (begin end buffer) from the Clojure error MESSAGE. |
|
|
If location could not be found, return nil." |
|
|
(save-excursion |
|
|
(let ((info (cider-extract-error-info cider-compilation-regexp message))) |
|
|
(when info |
|
|
(let ((file (nth 0 info)) |
|
|
(line (nth 1 info)) |
|
|
(col (nth 2 info))) |
|
|
(unless (or (not (stringp file)) |
|
|
(cider--tooling-file-p file)) |
|
|
(when-let ((buffer (cider-find-file file))) |
|
|
(with-current-buffer buffer |
|
|
(save-excursion |
|
|
(save-restriction |
|
|
(widen) |
|
|
(goto-char (point-min)) |
|
|
(forward-line (1- line)) |
|
|
(move-to-column (or col 0)) |
|
|
(let ((begin (progn (if col (cider--goto-expression-start) (back-to-indentation)) |
|
|
(point))) |
|
|
(end (progn (if col (forward-list) (move-end-of-line nil)) |
|
|
(point)))) |
|
|
(list begin end buffer)))))))))))) |
|
|
|
|
|
(defun cider-handle-compilation-errors (message eval-buffer) |
|
|
"Highlight and jump to compilation error extracted from MESSAGE. |
|
|
EVAL-BUFFER is the buffer that was current during user's interactive |
|
|
evaluation command. Honor `cider-auto-jump-to-error'." |
|
|
(when-let ((loc (cider--find-last-error-location message)) |
|
|
(overlay (make-overlay (nth 0 loc) (nth 1 loc) (nth 2 loc))) |
|
|
(info (cider-extract-error-info cider-compilation-regexp message))) |
|
|
(let* ((face (nth 3 info)) |
|
|
(note (nth 4 info)) |
|
|
(auto-jump (if (eq cider-auto-jump-to-error 'errors-only) |
|
|
(not (eq face 'cider-warning-highlight-face)) |
|
|
cider-auto-jump-to-error))) |
|
|
(overlay-put overlay 'cider-note-p t) |
|
|
(overlay-put overlay 'font-lock-face face) |
|
|
(overlay-put overlay 'cider-note note) |
|
|
(overlay-put overlay 'help-echo note) |
|
|
(overlay-put overlay 'modification-hooks |
|
|
(list (lambda (o &rest _args) (delete-overlay o)))) |
|
|
(when auto-jump |
|
|
(with-current-buffer eval-buffer |
|
|
(push-mark) |
|
|
;; At this stage selected window commonly is *cider-error* and we need to |
|
|
;; re-select the original user window. If eval-buffer is not |
|
|
;; visible it was probably covered as a result of a small screen or user |
|
|
;; configuration (https://github.com/clojure-emacs/cider/issues/847). In |
|
|
;; that case we don't jump at all in order to avoid covering *cider-error* |
|
|
;; buffer. |
|
|
(when-let ((win (get-buffer-window eval-buffer))) |
|
|
(with-selected-window win |
|
|
(cider-jump-to (nth 2 loc) (car loc))))))))) |
|
|
|
|
|
(defun cider-need-input (buffer) |
|
|
"Handle an need-input request from BUFFER." |
|
|
(with-current-buffer buffer |
|
|
(nrepl-request:stdin (concat (read-from-minibuffer "Stdin: ") "\n") |
|
|
(cider-stdin-handler buffer) |
|
|
(cider-current-connection) |
|
|
(cider-current-session)))) |
|
|
|
|
|
(defun cider-emit-into-color-buffer (buffer value) |
|
|
"Emit into color BUFFER the provided VALUE." |
|
|
(with-current-buffer buffer |
|
|
(let ((inhibit-read-only t) |
|
|
(buffer-undo-list t)) |
|
|
(goto-char (point-max)) |
|
|
(insert (format "%s" value)) |
|
|
(ansi-color-apply-on-region (point-min) (point-max))) |
|
|
(goto-char (point-min)))) |
|
|
|
|
|
|
|
|
;;; Evaluation |
|
|
|
|
|
(defvar cider-to-nrepl-filename-function |
|
|
(with-no-warnings |
|
|
(if (eq system-type 'cygwin) |
|
|
#'cygwin-convert-file-name-to-windows |
|
|
#'identity)) |
|
|
"Function to translate Emacs filenames to nREPL namestrings.") |
|
|
|
|
|
(defvar-local cider--ns-form-cache (make-hash-table :test 'equal) |
|
|
"ns form cache for the current buffer. |
|
|
|
|
|
The cache is a hash where the keys are connection names and the values |
|
|
are ns forms. This allows every connection to keep track of the ns |
|
|
form independently.") |
|
|
|
|
|
(defun cider--cache-ns-form () |
|
|
"Cache the form in the current buffer for the current connection." |
|
|
(puthash (cider-current-connection) |
|
|
(cider-ns-form) |
|
|
cider--ns-form-cache)) |
|
|
|
|
|
(defun cider--cached-ns-form () |
|
|
"Retrieve the cached ns form for the current buffer & connection." |
|
|
(gethash (cider-current-connection) cider--ns-form-cache)) |
|
|
|
|
|
(defun cider--prep-interactive-eval (form) |
|
|
"Prepares the environment for an interactive eval of FORM. |
|
|
|
|
|
If FORM is an ns-form, ensure that it is evaluated in the `user` |
|
|
namespace. Otherwise, ensure the current ns declaration has been |
|
|
evaluated (so that the ns containing FORM exists). |
|
|
|
|
|
Clears any compilation highlights and kills the error window." |
|
|
(cider--clear-compilation-highlights) |
|
|
(cider--quit-error-window) |
|
|
(let ((cur-ns-form (cider-ns-form))) |
|
|
(when (and cur-ns-form |
|
|
(not (string= cur-ns-form (cider--cached-ns-form))) |
|
|
(not (cider-ns-form-p form))) |
|
|
;; TODO: check for evaluation errors |
|
|
(cider-eval-ns-form 'sync) |
|
|
(cider--cache-ns-form)))) |
|
|
|
|
|
(defvar-local cider-interactive-eval-override nil |
|
|
"Function to call instead of `cider-interactive-eval'.") |
|
|
|
|
|
(defun cider-interactive-eval (form &optional callback bounds) |
|
|
"Evaluate FORM and dispatch the response to CALLBACK. |
|
|
This function is the main entry point in CIDER's interactive evaluation |
|
|
API. Most other interactive eval functions should rely on this function. |
|
|
If CALLBACK is nil use `cider-interactive-eval-handler'. |
|
|
BOUNDS, if non-nil, is a list of two numbers marking the start and end |
|
|
positions of FORM in its buffer. |
|
|
|
|
|
If `cider-interactive-eval-override' is a function, call it with the same |
|
|
arguments and only proceed with evaluation if it returns nil." |
|
|
(let ((form (or form (apply #'buffer-substring bounds))) |
|
|
(start (car-safe bounds)) |
|
|
(end (car-safe (cdr-safe bounds)))) |
|
|
(unless (and cider-interactive-eval-override |
|
|
(functionp cider-interactive-eval-override) |
|
|
(funcall cider-interactive-eval-override form callback bounds)) |
|
|
(cider--prep-interactive-eval form) |
|
|
(cider-spinner-start) |
|
|
(cider-nrepl-request:eval |
|
|
form |
|
|
(if cider-show-eval-spinner |
|
|
(cider-eval-spinner-handler |
|
|
(current-buffer) |
|
|
(or callback (cider-interactive-eval-handler nil end))) |
|
|
(or callback (cider-interactive-eval-handler nil end))) |
|
|
;; always eval ns forms in the user namespace |
|
|
;; otherwise trying to eval ns form for the first time will produce an error |
|
|
(if (cider-ns-form-p form) "user" (cider-current-ns)) |
|
|
start)))) |
|
|
|
|
|
(defun cider-interactive-pprint-eval (form &optional callback right-margin) |
|
|
"Evaluate FORM and dispatch the response to CALLBACK. |
|
|
This function is the same as `cider-interactive-eval', except the result is |
|
|
pretty-printed to *out*. RIGHT-MARGIN specifies the maximum column width of |
|
|
the printed result, and defaults to `fill-column'." |
|
|
(cider--prep-interactive-eval form) |
|
|
(cider-nrepl-request:pprint-eval |
|
|
form |
|
|
(or callback (cider-interactive-eval-handler)) |
|
|
;; always eval ns forms in the user namespace |
|
|
;; otherwise trying to eval ns form for the first time will produce an error |
|
|
(if (cider-ns-form-p form) "user" (cider-current-ns)) |
|
|
(or right-margin fill-column))) |
|
|
|
|
|
(defun cider-eval-region (start end) |
|
|
"Evaluate the region between START and END." |
|
|
(interactive "r") |
|
|
(cider-interactive-eval nil nil (list start end))) |
|
|
|
|
|
(defun cider-eval-last-sexp (&optional prefix) |
|
|
"Evaluate the expression preceding point. |
|
|
If invoked with a PREFIX argument, print the result in the current buffer." |
|
|
(interactive "P") |
|
|
(cider-interactive-eval nil |
|
|
(when prefix (cider-eval-print-handler)) |
|
|
(cider-last-sexp 'bounds))) |
|
|
|
|
|
(defun cider-eval-last-sexp-and-replace () |
|
|
"Evaluate the expression preceding point and replace it with its result." |
|
|
(interactive) |
|
|
(let ((last-sexp (cider-last-sexp))) |
|
|
;; we have to be sure the evaluation won't result in an error |
|
|
(cider-nrepl-sync-request:eval last-sexp) |
|
|
;; seems like the sexp is valid, so we can safely kill it |
|
|
(backward-kill-sexp) |
|
|
(cider-interactive-eval last-sexp (cider-eval-print-handler)))) |
|
|
|
|
|
(declare-function cider-switch-to-repl-buffer "cider-mode") |
|
|
|
|
|
(defun cider-eval-last-sexp-to-repl (&optional prefix) |
|
|
"Evaluate the expression preceding point and insert its result in the REPL. |
|
|
If invoked with a PREFIX argument, switch to the REPL buffer." |
|
|
(interactive "P") |
|
|
(cider-interactive-eval (cider-last-sexp) |
|
|
(cider-insert-eval-handler (cider-current-connection))) |
|
|
(when prefix |
|
|
(cider-switch-to-repl-buffer))) |
|
|
|
|
|
(defun cider-eval-print-last-sexp () |
|
|
"Evaluate the expression preceding point. |
|
|
Print its value into the current buffer." |
|
|
(interactive) |
|
|
(cider-interactive-eval (cider-last-sexp) |
|
|
(cider-eval-print-handler))) |
|
|
|
|
|
(defun cider--pprint-eval-form (form) |
|
|
"Pretty print FORM in popup buffer." |
|
|
(let* ((result-buffer (cider-popup-buffer cider-result-buffer nil 'clojure-mode)) |
|
|
(handler (cider-popup-eval-out-handler result-buffer)) |
|
|
(right-margin (max fill-column |
|
|
(1- (window-width (get-buffer-window result-buffer)))))) |
|
|
(cider-interactive-pprint-eval form handler right-margin))) |
|
|
|
|
|
(defun cider-pprint-eval-last-sexp () |
|
|
"Evaluate the sexp preceding point and pprint its value in a popup buffer." |
|
|
(interactive) |
|
|
(cider--pprint-eval-form (cider-last-sexp))) |
|
|
|
|
|
(defun cider-eval-defun-at-point (&optional debug-it) |
|
|
"Evaluate the current toplevel form, and print result in the minibuffer. |
|
|
With DEBUG-IT prefix argument, also debug the entire form as with the |
|
|
command `cider-debug-defun-at-point'." |
|
|
(interactive "P") |
|
|
(cider-interactive-eval |
|
|
(concat (if debug-it "#dbg ") |
|
|
(cider-defun-at-point)) |
|
|
nil |
|
|
(cider--region-for-defun-at-point))) |
|
|
|
|
|
(defun cider-pprint-eval-defun-at-point () |
|
|
"Evaluate the top-level form at point and pprint its value in a popup buffer." |
|
|
(interactive) |
|
|
(cider--pprint-eval-form (cider-defun-at-point))) |
|
|
|
|
|
(defun cider-eval-ns-form (&optional sync) |
|
|
"Evaluate the current buffer's namespace form. |
|
|
|
|
|
When SYNC is true the form is evaluated synchronously, |
|
|
otherwise it's evaluated interactively." |
|
|
(interactive) |
|
|
(when (clojure-find-ns) |
|
|
(save-excursion |
|
|
(goto-char (match-beginning 0)) |
|
|
(if sync |
|
|
(cider-nrepl-sync-request:eval (cider-defun-at-point)) |
|
|
(cider-eval-defun-at-point))))) |
|
|
|
|
|
(defun cider-read-and-eval () |
|
|
"Read a sexp from the minibuffer and output its result to the echo area." |
|
|
(interactive) |
|
|
(let* ((form (cider-read-from-minibuffer "CIDER Eval: ")) |
|
|
(override cider-interactive-eval-override) |
|
|
(ns-form (if (cider-ns-form-p form) "" (format "(ns %s)" (cider-current-ns))))) |
|
|
(with-current-buffer (get-buffer-create cider-read-eval-buffer) |
|
|
(erase-buffer) |
|
|
(clojure-mode) |
|
|
(unless (string= "" ns-form) |
|
|
(insert ns-form "\n\n")) |
|
|
(insert form) |
|
|
(let ((cider-interactive-eval-override override)) |
|
|
(cider-interactive-eval form))))) |
|
|
|
|
|
|
|
|
;; Connection and REPL |
|
|
|
|
|
(defun cider-insert-in-repl (form eval) |
|
|
"Insert FORM in the REPL buffer and switch to it. |
|
|
If EVAL is non-nil the form will also be evaluated." |
|
|
(while (string-match "\\`[ \t\n\r]+\\|[ \t\n\r]+\\'" form) |
|
|
(setq form (replace-match "" t t form))) |
|
|
(with-current-buffer (cider-current-connection) |
|
|
(goto-char (point-max)) |
|
|
(let ((beg (point))) |
|
|
(insert form) |
|
|
(indent-region beg (point))) |
|
|
(when eval |
|
|
(cider-repl-return))) |
|
|
(cider-switch-to-repl-buffer)) |
|
|
|
|
|
(defun cider-insert-last-sexp-in-repl (&optional arg) |
|
|
"Insert the expression preceding point in the REPL buffer. |
|
|
If invoked with a prefix ARG eval the expression after inserting it." |
|
|
(interactive "P") |
|
|
(cider-insert-in-repl (cider-last-sexp) arg)) |
|
|
|
|
|
(defun cider-insert-defun-in-repl (&optional arg) |
|
|
"Insert the top-level form at point in the REPL buffer. |
|
|
If invoked with a prefix ARG eval the expression after inserting it." |
|
|
(interactive "P") |
|
|
(cider-insert-in-repl (cider-defun-at-point) arg)) |
|
|
|
|
|
(defun cider-insert-region-in-repl (start end &optional arg) |
|
|
"Insert the curent region in the REPL buffer. |
|
|
If invoked with a prefix ARG eval the expression after inserting it." |
|
|
(interactive "rP") |
|
|
(cider-insert-in-repl |
|
|
(buffer-substring-no-properties start end) arg)) |
|
|
|
|
|
(defun cider-insert-ns-form-in-repl (&optional arg) |
|
|
"Insert the current buffer's ns form in the REPL buffer. |
|
|
If invoked with a prefix ARG eval the expression after inserting it." |
|
|
(interactive "P") |
|
|
(cider-insert-in-repl (cider-ns-form) arg)) |
|
|
|
|
|
(defun cider-ping () |
|
|
"Check that communication with the nREPL server works." |
|
|
(interactive) |
|
|
(thread-first (cider-nrepl-sync-request:eval "\"PONG\"") |
|
|
(nrepl-dict-get "value") |
|
|
(read) |
|
|
(message))) |
|
|
|
|
|
(defun cider-enable-on-existing-clojure-buffers () |
|
|
"Enable CIDER's minor mode on existing Clojure buffers. |
|
|
See command `cider-mode'." |
|
|
(interactive) |
|
|
(add-hook 'clojure-mode-hook #'cider-mode) |
|
|
(dolist (buffer (cider-util--clojure-buffers)) |
|
|
(with-current-buffer buffer |
|
|
(cider-mode +1)))) |
|
|
|
|
|
(defun cider-disable-on-existing-clojure-buffers () |
|
|
"Disable `cider-mode' on existing Clojure buffers. |
|
|
See command `cider-mode'." |
|
|
(interactive) |
|
|
(dolist (buffer (cider-util--clojure-buffers)) |
|
|
(with-current-buffer buffer |
|
|
(cider-mode -1)))) |
|
|
|
|
|
(defun cider-possibly-disable-on-existing-clojure-buffers () |
|
|
"If not connected, disable `cider-mode' on existing Clojure buffers." |
|
|
(unless (cider-connected-p) |
|
|
(cider-disable-on-existing-clojure-buffers))) |
|
|
|
|
|
|
|
|
;;; Completion |
|
|
|
|
|
(defun cider-sync-request:toggle-trace-var (symbol) |
|
|
"Toggle var tracing for SYMBOL." |
|
|
(cider-ensure-op-supported "toggle-trace-var") |
|
|
(thread-first (list "op" "toggle-trace-var" |
|
|
"ns" (cider-current-ns) |
|
|
"sym" symbol) |
|
|
(cider-nrepl-send-sync-request))) |
|
|
|
|
|
(defun cider--toggle-trace-var (sym) |
|
|
(let* ((trace-response (cider-sync-request:toggle-trace-var sym)) |
|
|
(var-name (nrepl-dict-get trace-response "var-name")) |
|
|
(var-status (nrepl-dict-get trace-response "var-status"))) |
|
|
(pcase var-status |
|
|
("not-found" (error "Var %s not found" sym)) |
|
|
("not-traceable" (error "Var %s can't be traced because it's not bound to a function" var-name)) |
|
|
(_ (message "Var %s %s" var-name var-status))))) |
|
|
|
|
|
(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." |
|
|
(interactive "P") |
|
|
(cider-ensure-op-supported "toggle-trace-var") |
|
|
(funcall (cider-prompt-for-symbol-function arg) |
|
|
"Toggle trace for var" |
|
|
#'cider--toggle-trace-var)) |
|
|
|
|
|
(defun cider-sync-request:toggle-trace-ns (ns) |
|
|
"Toggle namespace tracing for NS." |
|
|
(cider-ensure-op-supported "toggle-trace-ns") |
|
|
(thread-first (list "op" "toggle-trace-ns" |
|
|
"ns" ns) |
|
|
(cider-nrepl-send-sync-request))) |
|
|
|
|
|
(defun cider-toggle-trace-ns (query) |
|
|
"Toggle ns tracing. |
|
|
Defaults to the current ns. With prefix arg QUERY, prompts for a ns." |
|
|
(interactive "P") |
|
|
(cider-ensure-op-supported "toggle-trace-ns") |
|
|
(let ((ns (if query |
|
|
(completing-read "Toggle trace for ns: " (cider-sync-request:ns-list)) |
|
|
(cider-current-ns)))) |
|
|
(let* ((trace-response (cider-sync-request:toggle-trace-ns ns)) |
|
|
(ns-status (nrepl-dict-get trace-response "ns-status"))) |
|
|
(pcase ns-status |
|
|
("not-found" (error "ns %s not found" ns)) |
|
|
(_ (message "ns %s %s" ns ns-status)))))) |
|
|
|
|
|
(defun cider-undef () |
|
|
"Undefine the SYMBOL." |
|
|
(interactive) |
|
|
(cider-ensure-op-supported "undef") |
|
|
(cider-read-symbol-name |
|
|
"Undefine symbol: " |
|
|
(lambda (sym) |
|
|
(cider-nrepl-send-request |
|
|
(list "op" "undef" |
|
|
"ns" (cider-current-ns) |
|
|
"symbol" sym) |
|
|
(cider-interactive-eval-handler (current-buffer)))))) |
|
|
|
|
|
(defun cider-refresh--handle-response (response log-buffer) |
|
|
(nrepl-dbind-response response (out err reloading status error error-ns after before) |
|
|
(cl-flet* ((log (message &optional face) |
|
|
(cider-emit-into-popup-buffer log-buffer message face)) |
|
|
|
|
|
(log-echo (message &optional face) |
|
|
(log message face) |
|
|
(unless cider-refresh-show-log-buffer |
|
|
(let ((message-truncate-lines t)) |
|
|
(message "cider-refresh: %s" message))))) |
|
|
(cond (out |
|
|
(log out)) |
|
|
|
|
|
(err |
|
|
(log err 'font-lock-warning-face)) |
|
|
|
|
|
((member "invoking-before" status) |
|
|
(log-echo (format "Calling %s\n" before) 'font-lock-string-face)) |
|
|
|
|
|
((member "invoked-before" status) |
|
|
(log-echo (format "Successfully called %s\n" before) 'font-lock-string-face)) |
|
|
|
|
|
(reloading |
|
|
(log-echo (format "Reloading %s\n" reloading) 'font-lock-string-face)) |
|
|
|
|
|
((member "reloading" (nrepl-dict-keys response)) |
|
|
(log-echo "Nothing to reload\n" 'font-lock-string-face)) |
|
|
|
|
|
((member "ok" status) |
|
|
(log-echo "Reloading successful\n" 'font-lock-string-face)) |
|
|
|
|
|
(error-ns |
|
|
(log-echo (format "Error reloading %s\n" error-ns) 'font-lock-warning-face)) |
|
|
|
|
|
((member "invoking-after" status) |
|
|
(log-echo (format "Calling %s\n" after) 'font-lock-string-face)) |
|
|
|
|
|
((member "invoked-after" status) |
|
|
(log-echo (format "Successfully called %s\n" after) 'font-lock-string-face)))) |
|
|
|
|
|
(with-selected-window (or (get-buffer-window cider-refresh-log-buffer) |
|
|
(selected-window)) |
|
|
(with-current-buffer cider-refresh-log-buffer |
|
|
(goto-char (point-max)))) |
|
|
|
|
|
(when (member "error" status) |
|
|
(cider--render-stacktrace-causes error)))) |
|
|
|
|
|
(defun cider-refresh (&optional mode) |
|
|
"Reload modified and unloaded namespaces on the classpath. |
|
|
|
|
|
With a single prefix argument, or if MODE is `refresh-all', reload all |
|
|
namespaces on the classpath unconditionally. |
|
|
|
|
|
With a double prefix argument, or if MODE is `clear', clear the state of |
|
|
the namespace tracker before reloading. This is useful for recovering from |
|
|
some classes of error (for example, those caused by circular dependencies) |
|
|
that a normal reload would not otherwise recover from. The trade-off of |
|
|
clearing is that stale code from any deleted files may not be completely |
|
|
unloaded." |
|
|
(interactive "p") |
|
|
(cider-ensure-op-supported "refresh") |
|
|
(let ((log-buffer (or (get-buffer cider-refresh-log-buffer) |
|
|
(cider-make-popup-buffer cider-refresh-log-buffer))) |
|
|
(clear? (member mode '(clear 16))) |
|
|
(refresh-all? (member mode '(refresh-all 4)))) |
|
|
(when cider-refresh-show-log-buffer (cider-popup-buffer-display log-buffer)) |
|
|
(when clear? (cider-nrepl-send-sync-request (list "op" "refresh-clear"))) |
|
|
(cider-nrepl-send-request (append (list "op" (if refresh-all? "refresh-all" "refresh") |
|
|
"print-length" cider-stacktrace-print-length |
|
|
"print-level" cider-stacktrace-print-level) |
|
|
(when cider-refresh-before-fn (list "before" cider-refresh-before-fn)) |
|
|
(when cider-refresh-after-fn (list "after" cider-refresh-after-fn))) |
|
|
(lambda (response) |
|
|
(cider-refresh--handle-response response log-buffer))))) |
|
|
|
|
|
(defun cider-file-string (file) |
|
|
"Read the contents of a FILE and return as a string." |
|
|
(with-current-buffer (find-file-noselect file) |
|
|
(substring-no-properties (buffer-string)))) |
|
|
|
|
|
(defun cider-load-file (filename) |
|
|
"Load (eval) the Clojure file FILENAME in nREPL." |
|
|
(interactive (list |
|
|
(read-file-name "Load file: " nil nil nil |
|
|
(when (buffer-file-name) |
|
|
(file-name-nondirectory |
|
|
(buffer-file-name)))))) |
|
|
(cider-ensure-connected) |
|
|
(when-let ((buf (find-buffer-visiting filename))) |
|
|
(with-current-buffer buf |
|
|
(remove-overlays nil nil 'cider-type 'instrumented-defs) |
|
|
(cider--clear-compilation-highlights))) |
|
|
(cider--quit-error-window) |
|
|
(cider--cache-ns-form) |
|
|
(cider-request:load-file |
|
|
(cider-file-string filename) |
|
|
(funcall cider-to-nrepl-filename-function (cider--server-filename filename)) |
|
|
(file-name-nondirectory filename)) |
|
|
(message "Loading %s..." filename)) |
|
|
|
|
|
(defun cider-load-buffer (&optional buffer) |
|
|
"Load (eval) BUFFER's file in nREPL. |
|
|
If no buffer is provided the command acts on the current buffer. |
|
|
The heavy lifting is done by `cider-load-file'." |
|
|
(interactive) |
|
|
(check-parens) |
|
|
(setq buffer (or buffer (current-buffer))) |
|
|
(with-current-buffer buffer |
|
|
(unless buffer-file-name |
|
|
(user-error "Buffer `%s' is not associated with a file" (current-buffer))) |
|
|
(when (and cider-prompt-save-file-on-load |
|
|
(buffer-modified-p) |
|
|
(or (eq cider-prompt-save-file-on-load 'always-save) |
|
|
(y-or-n-p (format "Save file %s? " buffer-file-name)))) |
|
|
(save-buffer)) |
|
|
(cider-load-file buffer-file-name))) |
|
|
|
|
|
(defalias 'cider-eval-file 'cider-load-file |
|
|
"A convenience alias as some people are confused by the load-* names.") |
|
|
|
|
|
(defalias 'cider-eval-buffer 'cider-load-buffer |
|
|
"A convenience alias as some people are confused by the load-* names.") |
|
|
|
|
|
(defun cider--format-buffer (formatter) |
|
|
"Format the contents of the current buffer. |
|
|
|
|
|
Uses FORMATTER, a function of one argument, to convert the string contents |
|
|
of the buffer into a formatted string." |
|
|
(let* ((original (substring-no-properties (buffer-string))) |
|
|
(formatted (funcall formatter original))) |
|
|
(unless (equal original formatted) |
|
|
(erase-buffer) |
|
|
(insert formatted)))) |
|
|
|
|
|
(defun cider-format-buffer () |
|
|
"Format the Clojure code in the current buffer." |
|
|
(interactive) |
|
|
(cider--format-buffer #'cider-sync-request:format-code)) |
|
|
|
|
|
(defun cider-format-edn-buffer () |
|
|
"Format the EDN data in the current buffer." |
|
|
(interactive) |
|
|
(cider--format-buffer (lambda (edn) |
|
|
(cider-sync-request:format-edn edn fill-column)))) |
|
|
|
|
|
(defun cider--format-reindent (formatted start) |
|
|
"Reindent FORMATTED to align with buffer position START." |
|
|
(let* ((start-column (save-excursion (goto-char start) (current-column))) |
|
|
(indent-line (concat "\n" (make-string start-column ? )))) |
|
|
(replace-regexp-in-string "\n" indent-line formatted))) |
|
|
|
|
|
(defun cider--format-region (start end formatter) |
|
|
"Format the contents of the given region. |
|
|
|
|
|
START and END are the character positions of the start and end of the |
|
|
region. FORMATTER is a function of one argument which is used to convert |
|
|
the string contents of the region into a formatted string." |
|
|
(let* ((original (buffer-substring-no-properties start end)) |
|
|
(formatted (funcall formatter original)) |
|
|
(indented (cider--format-reindent formatted start))) |
|
|
(unless (equal original indented) |
|
|
(delete-region start end) |
|
|
(insert indented)))) |
|
|
|
|
|
(defun cider-format-region (start end) |
|
|
"Format the Clojure code in the current region." |
|
|
(interactive "r") |
|
|
(cider--format-region start end #'cider-sync-request:format-code)) |
|
|
|
|
|
(defun cider-format-edn-region (start end) |
|
|
"Format the EDN data in the current region." |
|
|
(interactive "r") |
|
|
(let* ((start-column (save-excursion (goto-char start) (current-column))) |
|
|
(right-margin (- fill-column start-column))) |
|
|
(cider--format-region start end |
|
|
(lambda (edn) |
|
|
(cider-sync-request:format-edn edn right-margin))))) |
|
|
|
|
|
(defun cider-format-defun () |
|
|
"Format the code in the current defun." |
|
|
(interactive) |
|
|
(save-excursion |
|
|
(mark-defun) |
|
|
(cider-format-region (region-beginning) (region-end)))) |
|
|
|
|
|
;;; interrupt evaluation |
|
|
(defun cider-interrupt-handler (buffer) |
|
|
"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) |
|
|
(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) selected-session)) |
|
|
(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) |
|
|
(let ((selected-session (completing-read "Close nREPL session: " (nrepl-sessions (cider-current-connection))))) |
|
|
(when selected-session |
|
|
(nrepl-sync-request:close (cider-current-connection) selected-session) |
|
|
(message "Closed nREPL session %s" selected-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 nrepl-session |
|
|
(nrepl-sync-request:close (cider-current-connection) nrepl-session)) |
|
|
(when nrepl-tooling-session |
|
|
(nrepl-sync-request:close (cider-current-connection) nrepl-tooling-session))) |
|
|
(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 (buffer-live-p buf-name) |
|
|
(kill-buffer buf-name)))) |
|
|
|
|
|
(defun cider--quit-connection (conn) |
|
|
"Quit the connection CONN." |
|
|
(when conn |
|
|
(cider--close-connection-buffer conn) |
|
|
;; clean the cached ns forms for this connection in all Clojure buffers |
|
|
(dolist (clojure-buffer (cider-util--clojure-buffers)) |
|
|
(with-current-buffer clojure-buffer |
|
|
(remhash conn cider--ns-form-cache))))) |
|
|
|
|
|
(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 |
|
|
(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? " connection)) |
|
|
(cider--quit-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-connection (conn) |
|
|
"Restart the connection CONN." |
|
|
(let ((project-dir (with-current-buffer conn nrepl-project-dir))) |
|
|
(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..." conn) |
|
|
(sleep-for 2) |
|
|
(if project-dir |
|
|
(let ((default-directory project-dir)) |
|
|
(cider-jack-in)) |
|
|
(error "Can't restart CIDER connection for unknown project")))) |
|
|
|
|
|
(defun cider-restart (&optional restart-all) |
|
|
"Restart the currently active CIDER connection. |
|
|
If RESTART-ALL is t, then restarts all connections." |
|
|
(interactive "P") |
|
|
(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.") |
|
|
|
|
|
(defun cider--var-namespace (var) |
|
|
"Return the namespace of VAR. |
|
|
VAR is a fully qualified Clojure variable name as a string." |
|
|
(replace-regexp-in-string "\\(?:#'\\)?\\(.*\\)/.*" "\\1" var)) |
|
|
|
|
|
(defun cider-run (&optional function) |
|
|
"Run -main or FUNCTION, prompting for its namespace if necessary. |
|
|
With a prefix argument, prompt for function to run instead of -main." |
|
|
(interactive (list (when current-prefix-arg (read-string "Function name: ")))) |
|
|
(let ((name (or function "-main"))) |
|
|
(when-let ((response (cider-nrepl-send-sync-request |
|
|
(list "op" "ns-list-vars-by-name" "name" name)))) |
|
|
(if-let ((vars (split-string (substring (nrepl-dict-get response "var-list") 1 -1)))) |
|
|
(cider-interactive-eval |
|
|
(if (= (length vars) 1) |
|
|
(concat "(" (car vars) ")") |
|
|
(let* ((completions (mapcar #'cider--var-namespace vars)) |
|
|
(def (or (car cider--namespace-history) |
|
|
(car completions)))) |
|
|
(format "(#'%s/%s)" |
|
|
(completing-read (format "Namespace (%s): " def) |
|
|
completions nil t nil |
|
|
'cider--namespace-history def) |
|
|
name)))) |
|
|
(user-error "No %s var defined in any namespace" name))))) |
|
|
|
|
|
(defconst cider-manual-url "https://github.com/clojure-emacs/cider/blob/master/README.md" |
|
|
"The URL to CIDER's manual.") |
|
|
|
|
|
(defun cider-view-manual () |
|
|
"View the manual in your default browser." |
|
|
(interactive) |
|
|
(browse-url cider-manual-url)) |
|
|
|
|
|
(defconst cider-report-bug-url "https://github.com/clojure-emacs/cider/issues/new" |
|
|
"The URL to report a CIDER issue.") |
|
|
|
|
|
(defun cider-report-bug () |
|
|
"Report a bug in your default browser." |
|
|
(interactive) |
|
|
(browse-url cider-report-bug-url)) |
|
|
|
|
|
(provide 'cider-interaction) |
|
|
|
|
|
;;; cider-interaction.el ends here
|
|
|
|