|
|
- ;;; cider-repl.el --- CIDER REPL mode interactions -*- lexical-binding: t -*-
-
- ;; Copyright © 2012-2013 Tim King, Phil Hagelberg, Bozhidar Batsov
- ;; Copyright © 2013-2019 Bozhidar Batsov, Artur Malabarba and CIDER contributors
- ;;
- ;; Author: Tim King <kingtim@gmail.com>
- ;; Phil Hagelberg <technomancy@gmail.com>
- ;; Bozhidar Batsov <bozhidar@batsov.com>
- ;; Artur Malabarba <bruce.connor.am@gmail.com>
- ;; Hugo Duncan <hugo@hugoduncan.org>
- ;; Steve Purcell <steve@sanityinc.com>
- ;; Reid McKenzie <me@arrdem.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:
-
- ;; This functionality concerns `cider-repl-mode' and REPL interaction. For
- ;; REPL/connection life-cycle management see cider-connection.el.
-
- ;;; Code:
-
- (require 'cl-lib)
- (require 'easymenu)
- (require 'image)
- (require 'map)
- (require 'seq)
- (require 'subr-x)
-
- (require 'clojure-mode)
- (require 'sesman)
-
- (require 'cider-client)
- (require 'cider-doc)
- (require 'cider-test)
- (require 'cider-eldoc) ; for cider-eldoc-setup
- (require 'cider-common)
- (require 'cider-compat)
- (require 'cider-util)
- (require 'cider-resolve)
-
- (eval-when-compile
- (defvar paredit-version)
- (defvar paredit-space-for-delimiter-predicates))
-
- (defgroup cider-repl nil
- "Interaction with the REPL."
- :prefix "cider-repl-"
- :group 'cider)
-
- (defface cider-repl-prompt-face
- '((t (:inherit font-lock-keyword-face)))
- "Face for the prompt in the REPL buffer."
- :group 'cider-repl)
-
- (defface cider-repl-stdout-face
- '((t (:inherit font-lock-string-face)))
- "Face for STDOUT output in the REPL buffer."
- :group 'cider-repl)
-
- (defface cider-repl-stderr-face
- '((t (:inherit font-lock-warning-face)))
- "Face for STDERR output in the REPL buffer."
- :group 'cider-repl
- :package-version '(cider . "0.6.0"))
-
- (defface cider-repl-input-face
- '((t (:bold t)))
- "Face for previous input in the REPL buffer."
- :group 'cider-repl)
-
- (defface cider-repl-result-face
- '((t ()))
- "Face for the result of an evaluation in the REPL buffer."
- :group 'cider-repl)
-
- (defcustom cider-repl-pop-to-buffer-on-connect t
- "Controls whether to pop to the REPL buffer on connect.
-
- When set to nil the buffer will only be created, and not displayed. When
- set to `display-only' the buffer will be displayed, but it will not become
- focused. Otherwise the buffer is displayed and focused."
- :type '(choice (const :tag "Create the buffer, but don't display it" nil)
- (const :tag "Create and display the buffer, but don't focus it"
- display-only)
- (const :tag "Create, display, and focus the buffer" t))
- :group 'cider-repl)
-
- (defcustom cider-repl-display-in-current-window nil
- "Controls whether the REPL buffer is displayed in the current window."
- :type 'boolean
- :group 'cider-repl)
-
- (make-obsolete-variable 'cider-repl-scroll-on-output 'scroll-conservatively "0.21")
-
- (defcustom cider-repl-use-pretty-printing t
- "Control whether results in the REPL are pretty-printed or not.
- The REPL will use the printer specified in `cider-pprint-fn'.
- The `cider-toggle-pretty-printing' command can be used to interactively
- change the setting's value."
- :type 'boolean
- :group 'cider-repl)
-
- (make-obsolete-variable 'cider-repl-pretty-print-width 'cider-print-options "0.21")
-
- (defcustom cider-repl-use-content-types t
- "Control whether REPL results are presented using content-type information.
- The `cider-repl-toggle-content-types' command can be used to interactively
- change the setting's value."
- :type 'boolean
- :group 'cider-repl
- :package-version '(cider . "0.17.0"))
-
- (defcustom cider-repl-auto-detect-type t
- "Control whether to auto-detect the REPL type using track-state information.
- If you disable this you'll have to manually change the REPL type between
- Clojure and ClojureScript when invoking REPL type changing forms.
- Use `cider-set-repl-type' to manually change the REPL type."
- :type 'boolean
- :group 'cider-repl
- :safe #'booleanp
- :package-version '(cider . "0.18.0"))
-
- (defcustom cider-repl-use-clojure-font-lock t
- "Non-nil means to use Clojure mode font-locking for input and result.
- Nil means that `cider-repl-input-face' and `cider-repl-result-face'
- will be used."
- :type 'boolean
- :group 'cider-repl
- :package-version '(cider . "0.10.0"))
-
- (defcustom cider-repl-require-ns-on-set nil
- "Controls whether to require the ns before setting it in the REPL."
- :type 'boolean
- :group 'cider-repl
- :package-version '(cider . "0.22.0"))
-
- (defcustom cider-repl-result-prefix ""
- "The prefix displayed in the REPL before a result value.
- By default there's no prefix, but you can specify something
- like \"=>\" if want results to stand out more."
- :type 'string
- :group 'cider
- :package-version '(cider . "0.5.0"))
-
- (defcustom cider-repl-tab-command 'cider-repl-indent-and-complete-symbol
- "Select the command to be invoked by the TAB key.
- The default option is `cider-repl-indent-and-complete-symbol'. If
- you'd like to use the default Emacs behavior use
- `indent-for-tab-command'."
- :type 'symbol
- :group 'cider-repl)
-
- (make-obsolete-variable 'cider-repl-print-length 'cider-print-options "0.21")
- (make-obsolete-variable 'cider-repl-print-level 'cider-print-options "0.21")
-
- (defvar cider-repl-require-repl-utils-code
- '((clj . "(clojure.core/apply clojure.core/require clojure.main/repl-requires)")
- (cljs . "(use '[cljs.repl :only [apropos dir doc find-doc print-doc pst source]])")))
-
- (defcustom cider-repl-init-code (list (cdr (assoc 'clj cider-repl-require-repl-utils-code)))
- "Clojure code to evaluate when starting a REPL.
- Will be evaluated with bindings for set!-able vars in place."
- :type '(list string)
- :group 'cider-repl
- :package-version '(cider . "0.21.0"))
-
- (defcustom cider-repl-display-help-banner t
- "When non-nil a bit of help text will be displayed on REPL start."
- :type 'boolean
- :group 'cider-repl
- :package-version '(cider . "0.11.0"))
-
- ;;;; REPL buffer local variables
- (defvar-local cider-repl-input-start-mark nil)
-
- (defvar-local cider-repl-prompt-start-mark nil)
-
- (defvar-local cider-repl-old-input-counter 0
- "Counter used to generate unique `cider-old-input' properties.
- This property value must be unique to avoid having adjacent inputs be
- joined together.")
-
- (defvar-local cider-repl-input-history '()
- "History list of strings read from the REPL buffer.")
-
- (defvar-local cider-repl-input-history-items-added 0
- "Variable counting the items added in the current session.")
-
- (defvar-local cider-repl-output-start nil
- "Marker for the start of output.
- Currently its only purpose is to facilitate `cider-repl-clear-buffer'.")
-
- (defvar-local cider-repl-output-end nil
- "Marker for the end of output.
- Currently its only purpose is to facilitate `cider-repl-clear-buffer'.")
-
- (defun cider-repl-tab ()
- "Invoked on TAB keystrokes in `cider-repl-mode' buffers."
- (interactive)
- (funcall cider-repl-tab-command))
-
- (defun cider-repl-reset-markers ()
- "Reset all REPL markers."
- (dolist (markname '(cider-repl-output-start
- cider-repl-output-end
- cider-repl-prompt-start-mark
- cider-repl-input-start-mark))
- (set markname (make-marker))
- (set-marker (symbol-value markname) (point))))
-
- ;;; REPL init
-
- (defvar-local cider-repl-ns-cache nil
- "A dict holding information about all currently loaded namespaces.
- This cache is stored in the connection buffer.")
-
- (defvar cider-mode)
- (declare-function cider-refresh-dynamic-font-lock "cider-mode")
-
- (defun cider-repl--state-handler (response)
- "Handle server state contained in RESPONSE."
- (with-demoted-errors "Error in `cider-repl--state-handler': %s"
- (when (member "state" (nrepl-dict-get response "status"))
- (nrepl-dbind-response response (repl-type changed-namespaces)
- (when (and repl-type cider-repl-auto-detect-type)
- (cider-set-repl-type repl-type))
- (unless (nrepl-dict-empty-p changed-namespaces)
- (setq cider-repl-ns-cache (nrepl-dict-merge cider-repl-ns-cache changed-namespaces))
- (dolist (b (buffer-list))
- (with-current-buffer b
- ;; Metadata changed, so signatures may have changed too.
- (setq cider-eldoc-last-symbol nil)
- (when (or cider-mode (derived-mode-p 'cider-repl-mode))
- (when-let* ((ns-dict (or (nrepl-dict-get changed-namespaces (cider-current-ns))
- (let ((ns-dict (cider-resolve--get-in (cider-current-ns))))
- (when (seq-find (lambda (ns) (nrepl-dict-get changed-namespaces ns))
- (nrepl-dict-get ns-dict "aliases"))
- ns-dict)))))
- (cider-refresh-dynamic-font-lock ns-dict))))))))))
-
- (defun cider-repl-require-repl-utils ()
- "Require standard REPL util functions into the current REPL."
- (interactive)
- (let* ((current-repl (cider-current-repl nil 'ensure))
- (require-code (cdr (assoc (cider-repl-type current-repl) cider-repl-require-repl-utils-code))))
- (nrepl-send-sync-request
- (lax-plist-put
- (nrepl--eval-request require-code)
- "inhibit-cider-middleware" "true")
- current-repl)))
-
- (defun cider-repl-init-eval-handler (&optional callback)
- "Make an nREPL evaluation handler for use during REPL init.
- Run CALLBACK once the evaluation is complete."
- (nrepl-make-response-handler (current-buffer)
- (lambda (_buffer _value))
- (lambda (buffer out)
- (cider-repl-emit-stdout buffer out))
- (lambda (buffer err)
- (cider-repl-emit-stderr buffer err))
- (lambda (buffer)
- (cider-repl-emit-prompt buffer)
- (when callback
- (funcall callback)))))
-
- (defun cider-repl-eval-init-code (&optional callback)
- "Evaluate `cider-repl-init-code' in the current REPL.
- Run CALLBACK once the evaluation is complete."
- (interactive)
- (let* ((request (map-merge 'hash-table
- (cider--repl-request-map fill-column)
- '(("inhibit-cider-middleware" "true")))))
- (cider-nrepl-request:eval
- ;; Ensure we evaluate _something_ so the initial namespace is correctly set
- (thread-first (or cider-repl-init-code '("nil"))
- (string-join "\n"))
- (cider-repl-init-eval-handler callback)
- nil
- (line-number-at-pos (point))
- (cider-column-number-at-pos (point))
- (thread-last request
- (map-pairs)
- (seq-mapcat #'identity)))))
-
- (defun cider-repl-init (buffer &optional callback)
- "Initialize the REPL in BUFFER.
- BUFFER must be a REPL buffer with `cider-repl-mode' and a running
- client process connection. CALLBACK will be run once the REPL is
- fully initialized."
- (when cider-repl-display-in-current-window
- (add-to-list 'same-window-buffer-names (buffer-name buffer)))
- (pcase cider-repl-pop-to-buffer-on-connect
- (`display-only
- (let ((orig-buffer (current-buffer)))
- (display-buffer buffer)
- ;; User popup-rules (specifically `:select nil') can cause the call to
- ;; `display-buffer' to reset the current Emacs buffer to the clj/cljs
- ;; buffer that the user ran `jack-in' from - we need the current-buffer
- ;; to be the repl to initialize, so reset it back here to be resilient
- ;; against user config
- (set-buffer orig-buffer)))
- ((pred identity) (pop-to-buffer buffer)))
- (with-current-buffer buffer
- (cider-repl--insert-banner)
- (when-let* ((window (get-buffer-window buffer t)))
- (with-selected-window window
- (recenter (- -1 scroll-margin))))
- (cider-repl-eval-init-code callback))
- buffer)
-
- (defun cider-repl--insert-banner ()
- "Insert the banner in the current REPL buffer."
- (insert-before-markers
- (propertize (cider-repl--banner) 'font-lock-face 'font-lock-comment-face))
- (when cider-repl-display-help-banner
- (insert-before-markers
- (propertize (cider-repl--help-banner) 'font-lock-face 'font-lock-comment-face))))
-
- (defun cider-repl--banner ()
- "Generate the welcome REPL buffer banner."
- (format ";; Connected to nREPL server - nrepl://%s:%s
- ;; CIDER %s, nREPL %s
- ;; Clojure %s, Java %s
- ;; Docs: (doc function-name)
- ;; (find-doc part-of-name)
- ;; Source: (source function-name)
- ;; Javadoc: (javadoc java-object-or-class)
- ;; Exit: <C-c C-q>
- ;; Results: Stored in vars *1, *2, *3, an exception in *e;
- "
- (plist-get nrepl-endpoint :host)
- (plist-get nrepl-endpoint :port)
- (cider--version)
- (cider--nrepl-version)
- (cider--clojure-version)
- (cider--java-version)))
-
- (defun cider-repl--help-banner ()
- "Generate the help banner."
- (substitute-command-keys
- ";; ======================================================================
- ;; If you're new to CIDER it is highly recommended to go through its
- ;; manual first. Type <M-x cider-view-manual> to view it.
- ;; In case you're seeing any warnings you should consult the manual's
- ;; \"Troubleshooting\" section.
- ;;
- ;; Here are few tips to get you started:
- ;;
- ;; * Press <\\[describe-mode]> to see a list of the keybindings available (this
- ;; will work in every Emacs buffer)
- ;; * Press <\\[cider-repl-handle-shortcut]> to quickly invoke some REPL command
- ;; * Press <\\[cider-switch-to-last-clojure-buffer]> to switch between the REPL and a Clojure file
- ;; * Press <\\[cider-find-var]> to jump to the source of something (e.g. a var, a
- ;; Java method)
- ;; * Press <\\[cider-doc]> to view the documentation for something (e.g.
- ;; a var, a Java method)
- ;; * Enable `eldoc-mode' to display function & method signatures in the minibuffer.
- ;; * Print CIDER's refcard and keep it close to your keyboard.
- ;;
- ;; CIDER is super customizable - try <M-x customize-group cider> to
- ;; get a feel for this. If you're thirsty for knowledge you should try
- ;; <M-x cider-drink-a-sip>.
- ;;
- ;; If you think you've encountered a bug (or have some suggestions for
- ;; improvements) use <M-x cider-report-bug> to report it.
- ;;
- ;; Above all else - don't panic! In case of an emergency - procure
- ;; some (hard) cider and enjoy it responsibly!
- ;;
- ;; You can remove this message with the <M-x cider-repl-clear-help-banner> command.
- ;; You can disable it from appearing on start by setting
- ;; `cider-repl-display-help-banner' to nil.
- ;; ======================================================================
- "))
-
- ;;; REPL interaction
-
- (defun cider-repl--in-input-area-p ()
- "Return t if in input area."
- (<= cider-repl-input-start-mark (point)))
-
- (defun cider-repl--current-input (&optional until-point-p)
- "Return the current input as string.
- The input is the region from after the last prompt to the end of
- buffer. If UNTIL-POINT-P is non-nil, the input is until the current
- point."
- (buffer-substring-no-properties cider-repl-input-start-mark
- (if until-point-p
- (point)
- (point-max))))
-
- (defun cider-repl-previous-prompt ()
- "Move backward to the previous prompt."
- (interactive)
- (cider-repl--find-prompt t))
-
- (defun cider-repl-next-prompt ()
- "Move forward to the next prompt."
- (interactive)
- (cider-repl--find-prompt))
-
- (defun cider-repl--find-prompt (&optional backward)
- "Find the next prompt.
- If BACKWARD is non-nil look backward."
- (let ((origin (point))
- (cider-repl-prompt-property 'field))
- (while (progn
- (cider-search-property-change cider-repl-prompt-property backward)
- (not (or (cider-end-of-proprange-p cider-repl-prompt-property) (bobp) (eobp)))))
- (unless (cider-end-of-proprange-p cider-repl-prompt-property)
- (goto-char origin))))
-
- (defun cider-search-property-change (prop &optional backward)
- "Search forward for a property change to PROP.
- If BACKWARD is non-nil search backward."
- (cond (backward
- (goto-char (previous-single-char-property-change (point) prop)))
- (t
- (goto-char (next-single-char-property-change (point) prop)))))
-
- (defun cider-end-of-proprange-p (property)
- "Return t if at the the end of a property range for PROPERTY."
- (and (get-char-property (max (point-min) (1- (point))) property)
- (not (get-char-property (point) property))))
-
- (defun cider-repl--mark-input-start ()
- "Mark the input start."
- (set-marker cider-repl-input-start-mark (point) (current-buffer)))
-
- (defun cider-repl--mark-output-start ()
- "Mark the output start."
- (set-marker cider-repl-output-start (point))
- (set-marker cider-repl-output-end (point)))
-
- (defun cider-repl-mode-beginning-of-defun (&optional arg)
- "Move to the beginning of defun.
- If given a negative value of ARG, move to the end of defun."
- (if (and arg (< arg 0))
- (cider-repl-mode-end-of-defun (- arg))
- (dotimes (_ (or arg 1))
- (cider-repl-previous-prompt))))
-
- (defun cider-repl-mode-end-of-defun (&optional arg)
- "Move to the end of defun.
- If given a negative value of ARG, move to the beginning of defun."
- (if (and arg (< arg 0))
- (cider-repl-mode-beginning-of-defun (- arg))
- (dotimes (_ (or arg 1))
- (cider-repl-next-prompt))))
-
- (defun cider-repl-beginning-of-defun ()
- "Move to beginning of defun."
- (interactive)
- ;; We call `beginning-of-defun' if we're at the start of a prompt
- ;; already, to trigger `cider-repl-mode-beginning-of-defun' by means
- ;; of the locally bound `beginning-of-defun-function', in order to
- ;; jump to the start of the previous prompt.
- (if (and (not (cider-repl--at-prompt-start-p))
- (cider-repl--in-input-area-p))
- (goto-char cider-repl-input-start-mark)
- (beginning-of-defun)))
-
- (defun cider-repl-end-of-defun ()
- "Move to end of defun."
- (interactive)
- ;; C.f. `cider-repl-beginning-of-defun'
- (if (and (not (= (point) (point-max)))
- (cider-repl--in-input-area-p))
- (goto-char (point-max))
- (end-of-defun)))
-
- (defun cider-repl-bol-mark ()
- "Set the mark and go to the beginning of line or the prompt."
- (interactive)
- (unless mark-active
- (set-mark (point)))
- (move-beginning-of-line 1))
-
- (defun cider-repl--at-prompt-start-p ()
- "Return t if point is at the start of prompt.
- This will not work on non-current prompts."
- (= (point) cider-repl-input-start-mark))
-
- (defmacro cider-save-marker (marker &rest body)
- "Save MARKER and execute BODY."
- (declare (debug t))
- (let ((pos (make-symbol "pos")))
- `(let ((,pos (marker-position ,marker)))
- (prog1 (progn . ,body)
- (set-marker ,marker ,pos)))))
-
- (put 'cider-save-marker 'lisp-indent-function 1)
-
- (defun cider-repl-prompt-default (namespace)
- "Return a prompt string that mentions NAMESPACE."
- (format "%s> " namespace))
-
- (defun cider-repl-prompt-abbreviated (namespace)
- "Return a prompt string that abbreviates NAMESPACE."
- (format "%s> " (cider-abbreviate-ns namespace)))
-
- (defun cider-repl-prompt-lastname (namespace)
- "Return a prompt string with the last name in NAMESPACE."
- (format "%s> " (cider-last-ns-segment namespace)))
-
- (defcustom cider-repl-prompt-function #'cider-repl-prompt-default
- "A function that returns a prompt string.
- Takes one argument, a namespace name.
- For convenience, three functions are already provided for this purpose:
- `cider-repl-prompt-lastname', `cider-repl-prompt-abbreviated', and
- `cider-repl-prompt-default'"
- :type '(choice (const :tag "Full namespace" cider-repl-prompt-default)
- (const :tag "Abbreviated namespace" cider-repl-prompt-abbreviated)
- (const :tag "Last name in namespace" cider-repl-prompt-lastname)
- (function :tag "Custom function"))
- :group 'cider-repl
- :package-version '(cider . "0.9.0"))
-
- (defun cider-repl--insert-prompt (namespace)
- "Insert the prompt (before markers!), taking into account NAMESPACE.
- Set point after the prompt.
- Return the position of the prompt beginning."
- (goto-char cider-repl-input-start-mark)
- (cider-save-marker cider-repl-output-start
- (cider-save-marker cider-repl-output-end
- (unless (bolp) (insert-before-markers "\n"))
- (let ((prompt-start (point))
- (prompt (funcall cider-repl-prompt-function namespace)))
- (cider-propertize-region
- '(font-lock-face cider-repl-prompt-face read-only t intangible t
- field cider-repl-prompt
- rear-nonsticky (field read-only font-lock-face intangible))
- (insert-before-markers prompt))
- (set-marker cider-repl-prompt-start-mark prompt-start)
- prompt-start))))
-
- (defun cider-repl--flush-ansi-color-context ()
- "Flush ansi color context after printing.
- When there is a possible unfinished ansi control sequence,
- `ansi-color-context` maintains this list."
- (when (and ansi-color-context (stringp (cadr ansi-color-context)))
- (insert-before-markers (cadr ansi-color-context))
- (setq ansi-color-context nil)))
-
- (defvar-local cider-repl--ns-forms-plist nil
- "Plist holding ns->ns-form mappings within each connection.")
-
- (defun cider-repl--ns-form-changed-p (ns-form connection)
- "Return non-nil if NS-FORM for CONNECTION changed since last eval."
- (when-let* ((ns (cider-ns-from-form ns-form)))
- (not (string= ns-form
- (lax-plist-get
- (buffer-local-value 'cider-repl--ns-forms-plist connection)
- ns)))))
-
- (defvar cider-repl--root-ns-highlight-template "\\_<\\(%s\\)[^$/: \t\n()]+"
- "Regexp used to highlight root ns in REPL buffers.")
-
- (defvar-local cider-repl--root-ns-regexp nil
- "Cache of root ns regexp in REPLs.")
-
- (defvar-local cider-repl--ns-roots nil
- "List holding all past root namespaces seen during interactive eval.")
-
- (defun cider-repl--cache-ns-form (ns-form connection)
- "Given NS-FORM cache root ns in CONNECTION."
- (with-current-buffer connection
- (when-let* ((ns (cider-ns-from-form ns-form)))
- ;; cache ns-form
- (setq cider-repl--ns-forms-plist
- (lax-plist-put cider-repl--ns-forms-plist ns ns-form))
- ;; cache ns roots regexp
- (when (string-match "\\([^.]+\\)" ns)
- (let ((root (match-string-no-properties 1 ns)))
- (unless (member root cider-repl--ns-roots)
- (push root cider-repl--ns-roots)
- (let ((roots (mapconcat
- ;; Replace _ or - with regexp pattern to accommodate "raw" namespaces
- (lambda (r) (replace-regexp-in-string "[_-]+" "[_-]+" r))
- cider-repl--ns-roots "\\|")))
- (setq cider-repl--root-ns-regexp
- (format cider-repl--root-ns-highlight-template roots)))))))))
-
- (defvar cider-repl-spec-keywords-regexp
- (concat
- (regexp-opt '("In:" " val:"
- " at:" "fails at:"
- " spec:" "fails spec:"
- " predicate:" "fails predicate:"))
- "\\|^"
- (regexp-opt '(":clojure.spec.alpha/spec"
- ":clojure.spec.alpha/value")
- "\\("))
- "Regexp matching clojure.spec `explain` keywords.")
-
- (defun cider-repl-highlight-spec-keywords (string)
- "Highlight clojure.spec `explain` keywords in STRING.
- Foreground of `clojure-keyword-face' is used for highlight."
- (cider-add-face cider-repl-spec-keywords-regexp
- 'clojure-keyword-face t nil string)
- string)
-
- (defun cider-repl-highlight-current-project (string)
- "Fontify project's root namespace to make stacktraces more readable.
- Foreground of `cider-stacktrace-ns-face' is used to propertize matched
- namespaces. STRING is REPL's output."
- (cider-add-face cider-repl--root-ns-regexp 'cider-stacktrace-ns-face
- t nil string)
- string)
-
- (defun cider-repl-add-locref-help-echo (string)
- "Set help-echo property of STRING to `cider-locref-help-echo'."
- (put-text-property 0 (length string) 'help-echo 'cider-locref-help-echo string)
- string)
-
- (defvar cider-repl-preoutput-hook '(ansi-color-apply
- cider-repl-highlight-current-project
- cider-repl-highlight-spec-keywords
- cider-repl-add-locref-help-echo)
- "Hook run on output string before it is inserted into the REPL buffer.
- Each functions takes a string and must return a modified string. Also see
- `cider-run-chained-hook'.")
-
- (defun cider-repl--emit-output (buffer string face)
- "Using BUFFER, emit STRING as output font-locked using FACE.
- Before inserting, run `cider-repl-preoutput-hook' on STRING."
- (with-current-buffer buffer
- (save-excursion
- (cider-save-marker cider-repl-output-start
- (goto-char cider-repl-output-end)
- (setq string (propertize string
- 'font-lock-face face
- 'rear-nonsticky '(font-lock-face)))
- (setq string (cider-run-chained-hook 'cider-repl-preoutput-hook string))
- (insert-before-markers string)
- (cider-repl--flush-ansi-color-context))
- (when (and (= (point) cider-repl-prompt-start-mark)
- (not (bolp)))
- (insert-before-markers "\n")
- (set-marker cider-repl-output-end (1- (point))))))
- (when-let* ((window (get-buffer-window buffer t)))
- ;; If the prompt is on the first line of the window, then scroll the window
- ;; down by a single line to make the emitted output visible.
- (when (and (pos-visible-in-window-p cider-repl-prompt-start-mark window)
- (< 1 cider-repl-prompt-start-mark)
- (not (pos-visible-in-window-p (1- cider-repl-prompt-start-mark) window)))
- (with-selected-window window
- (scroll-down 1)))))
-
- (defun cider-repl--emit-interactive-output (string face)
- "Emit STRING as interactive output using FACE."
- (cider-repl--emit-output (cider-current-repl) string face))
-
- (defun cider-repl-emit-interactive-stdout (string)
- "Emit STRING as interactive output."
- (cider-repl--emit-interactive-output string 'cider-repl-stdout-face))
-
- (defun cider-repl-emit-interactive-stderr (string)
- "Emit STRING as interactive err output."
- (cider-repl--emit-interactive-output string 'cider-repl-stderr-face))
-
- (defun cider-repl-emit-stdout (buffer string)
- "Using BUFFER, emit STRING as standard output."
- (cider-repl--emit-output buffer string 'cider-repl-stdout-face))
-
- (defun cider-repl-emit-stderr (buffer string)
- "Using BUFFER, emit STRING as error output."
- (cider-repl--emit-output buffer string 'cider-repl-stderr-face))
-
- (defun cider-repl-emit-prompt (buffer)
- "Emit the REPL prompt into BUFFER."
- (with-current-buffer buffer
- (save-excursion
- (cider-repl--insert-prompt cider-buffer-ns))))
-
- (defun cider-repl-emit-result (buffer string show-prefix &optional bol)
- "Emit into BUFFER the result STRING and mark it as an evaluation result.
- If SHOW-PREFIX is non-nil insert `cider-repl-result-prefix' at the beginning
- of the line. If BOL is non-nil insert at the beginning of the line."
- (with-current-buffer buffer
- (save-excursion
- (cider-save-marker cider-repl-output-start
- (goto-char cider-repl-output-end)
- (when (and bol (not (bolp)))
- (insert-before-markers "\n"))
- (when show-prefix
- (insert-before-markers (propertize cider-repl-result-prefix 'font-lock-face 'font-lock-comment-face)))
- (if cider-repl-use-clojure-font-lock
- (insert-before-markers (cider-font-lock-as-clojure string))
- (cider-propertize-region
- '(font-lock-face cider-repl-result-face rear-nonsticky (font-lock-face))
- (insert-before-markers string)))))))
-
- (defun cider-repl-newline-and-indent ()
- "Insert a newline, then indent the next line.
- Restrict the buffer from the prompt for indentation, to avoid being
- confused by strange characters (like unmatched quotes) appearing
- earlier in the buffer."
- (interactive)
- (save-restriction
- (narrow-to-region cider-repl-prompt-start-mark (point-max))
- (insert "\n")
- (lisp-indent-line)))
-
- (defun cider-repl-indent-and-complete-symbol ()
- "Indent the current line and perform symbol completion.
- First indent the line. If indenting doesn't move point, complete
- the symbol."
- (interactive)
- (let ((pos (point)))
- (lisp-indent-line)
- (when (= pos (point))
- (if (save-excursion (re-search-backward "[^() \n\t\r]+\\=" nil t))
- (completion-at-point)))))
-
- (defun cider-repl-kill-input ()
- "Kill all text from the prompt to point."
- (interactive)
- (cond ((< (marker-position cider-repl-input-start-mark) (point))
- (kill-region cider-repl-input-start-mark (point)))
- ((= (point) (marker-position cider-repl-input-start-mark))
- (cider-repl-delete-current-input))))
-
- (defun cider-repl--input-complete-p (start end)
- "Return t if the region from START to END is a complete sexp."
- (save-excursion
- (goto-char start)
- (cond ((looking-at-p "\\s *[@'`#]?[(\"]")
- (ignore-errors
- (save-restriction
- (narrow-to-region start end)
- ;; Keep stepping over blanks and sexps until the end of
- ;; buffer is reached or an error occurs. Tolerate extra
- ;; close parens.
- (cl-loop do (skip-chars-forward " \t\r\n)")
- until (eobp)
- do (forward-sexp))
- t)))
- (t t))))
-
- (defun cider-repl--display-image (buffer image &optional show-prefix bol)
- "Insert IMAGE into BUFFER at the current point.
-
- For compatibility with the rest of CIDER's REPL machinery, supports
- SHOW-PREFIX and BOL."
- (with-current-buffer buffer
- (save-excursion
- (cider-save-marker cider-repl-output-start
- (goto-char cider-repl-output-end)
- (when (and bol (not (bolp)))
- (insert-before-markers "\n"))
- (when show-prefix
- (insert-before-markers
- (propertize cider-repl-result-prefix 'font-lock-face 'font-lock-comment-face)))
- ;; The below is inlined from `insert-image' and changed to use
- ;; `insert-before-markers' rather than `insert'
- (let ((start (point))
- (props (nconc `(display ,image rear-nonsticky (display))
- (when (boundp 'image-map)
- `(keymap ,image-map)))))
- (insert-before-markers " ")
- (add-text-properties start (point) props)))))
- t)
-
- (defcustom cider-repl-image-margin 10
- "Specifies the margin to be applied to images displayed in the REPL.
- Either a single number of pixels - interpreted as a symmetric margin, or
- pair of numbers `(x . y)' encoding an arbitrary margin."
- :type '(choice integer (vector integer integer))
- :group 'cider-repl
- :package-version '(cider . "0.17.0"))
-
- (defun cider-repl--image (data type datap)
- "A helper for creating images with CIDER's image options.
- DATA is either the path to an image or its base64 coded data. TYPE is a
- symbol indicating the image type. DATAP indicates whether the image is the
- raw image data or a filename. Returns an image instance with a margin per
- `cider-repl-image-margin'."
- (create-image data type datap
- :margin cider-repl-image-margin))
-
- (defun cider-repl-handle-jpeg (_type buffer image &optional show-prefix bol)
- "A handler for inserting a jpeg IMAGE into a repl BUFFER.
- Part of the default `cider-repl-content-type-handler-alist'."
- (cider-repl--display-image buffer
- (cider-repl--image image 'jpeg t)
- show-prefix bol))
-
- (defun cider-repl-handle-png (_type buffer image &optional show-prefix bol)
- "A handler for inserting a png IMAGE into a repl BUFFER.
- Part of the default `cider-repl-content-type-handler-alist'."
- (cider-repl--display-image buffer
- (cider-repl--image image 'png t)
- show-prefix bol))
-
- (defun cider-repl-handle-external-body (type buffer _ &optional _show-prefix _bol)
- "Handler for slurping external content into BUFFER.
- Handles an external-body TYPE by issuing a slurp request to fetch the content."
- (if-let* ((args (cadr type))
- (access-type (nrepl-dict-get args "access-type")))
- (nrepl-send-request
- (list "op" "slurp" "url" (nrepl-dict-get args access-type))
- (cider-repl-handler buffer)
- (cider-current-repl)))
- nil)
-
- (defvar cider-repl-content-type-handler-alist
- `(("message/external-body" . ,#'cider-repl-handle-external-body)
- ("image/jpeg" . ,#'cider-repl-handle-jpeg)
- ("image/png" . ,#'cider-repl-handle-png))
- "Association list from content-types to handlers.
- Handlers must be functions of two required and two optional arguments - the
- REPL buffer to insert into, the value of the given content type as a raw
- string, the REPL's show prefix as any and an `end-of-line' flag.
-
- The return value of the handler should be a flag, indicating whether or not
- the REPL is ready for a prompt to be displayed. Most handlers should return
- t, as the content-type response is (currently) an alternative to the
- value response. However for handlers which themselves issue subsequent
- nREPL ops, it may be convenient to prevent inserting a prompt.")
-
- (defun cider-repl-handler (buffer)
- "Make an nREPL evaluation handler for the REPL BUFFER."
- (let ((show-prompt t))
- (nrepl-make-response-handler
- 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))
- (lambda (buffer)
- (when show-prompt
- (cider-repl-emit-prompt buffer)))
- nrepl-err-handler
- (lambda (buffer value content-type)
- (if-let* ((content-attrs (cadr content-type))
- (content-type* (car content-type))
- (handler (cdr (assoc content-type*
- cider-repl-content-type-handler-alist))))
- (setq show-prompt (funcall handler content-type buffer value nil t))
- (cider-repl-emit-result buffer value t t)))
- (lambda (buffer warning)
- (cider-repl-emit-stderr buffer warning)))))
-
- (defun cider--repl-request-map (right-margin)
- "Map to be merged into REPL eval requests.
- RIGHT-MARGIN is as in `cider--nrepl-print-request-map'."
- (map-merge 'hash-table
- (cider--nrepl-print-request-map right-margin)
- (unless cider-repl-use-pretty-printing
- '(("nrepl.middleware.print/print" "cider.nrepl.pprint/pr")))
- (when cider-repl-use-content-types
- (cider--nrepl-content-type-map))))
-
- (defun cider-repl--send-input (&optional newline)
- "Go to the end of the input and send the current input.
- If NEWLINE is true then add a newline at the end of the input."
- (unless (cider-repl--in-input-area-p)
- (error "No input at point"))
- (let ((input (cider-repl--current-input)))
- (if (string-blank-p input)
- ;; don't evaluate a blank string, but erase it and emit
- ;; a fresh prompt to acknowledge to the user.
- (progn
- (cider-repl--replace-input "")
- (cider-repl-emit-prompt (current-buffer)))
- ;; otherwise evaluate the input
- (goto-char (point-max))
- (let ((end (point))) ; end of input, without the newline
- (cider-repl--add-to-input-history input)
- (when newline
- (insert "\n"))
- (let ((inhibit-modification-hooks t))
- (add-text-properties cider-repl-input-start-mark
- (point)
- `(cider-old-input
- ,(cl-incf cider-repl-old-input-counter))))
- (unless cider-repl-use-clojure-font-lock
- (let ((overlay (make-overlay cider-repl-input-start-mark end)))
- ;; These properties are on an overlay so that they won't be taken
- ;; by kill/yank.
- (overlay-put overlay 'read-only t)
- (overlay-put overlay 'font-lock-face 'cider-repl-input-face))))
- (let ((input-start (save-excursion (cider-repl-beginning-of-defun) (point))))
- (goto-char (point-max))
- (cider-repl--mark-input-start)
- (cider-repl--mark-output-start)
- (cider-nrepl-request:eval
- input
- (cider-repl-handler (current-buffer))
- (cider-current-ns)
- (line-number-at-pos input-start)
- (cider-column-number-at-pos input-start)
- (thread-last
- (cider--repl-request-map fill-column)
- (map-pairs)
- (seq-mapcat #'identity)))))))
-
- (defun cider-repl-return (&optional end-of-input)
- "Evaluate the current input string, or insert a newline.
- Send the current input ony if a whole expression has been entered,
- i.e. the parenthesis are matched.
- When END-OF-INPUT is non-nil, send the input even if the parentheses
- are not balanced."
- (interactive "P")
- (cond
- (end-of-input
- (cider-repl--send-input))
- ((and (get-text-property (point) 'cider-old-input)
- (< (point) cider-repl-input-start-mark))
- (cider-repl--grab-old-input end-of-input))
- ((cider-repl--input-complete-p cider-repl-input-start-mark (point-max))
- (cider-repl--send-input t))
- (t
- (cider-repl-newline-and-indent)
- (message "[input not complete]"))))
-
- (defun cider-repl--grab-old-input (replace)
- "Resend the old REPL input at point.
- If REPLACE is non-nil the current input is replaced with the old
- input; otherwise the new input is appended. The old input has the
- text property `cider-old-input'."
- (cl-multiple-value-bind (beg end) (cider-property-bounds 'cider-old-input)
- (let ((old-input (buffer-substring beg end)) ;;preserve
- ;;properties, they will be removed later
- (offset (- (point) beg)))
- ;; Append the old input or replace the current input
- (cond (replace (goto-char cider-repl-input-start-mark))
- (t (goto-char (point-max))
- (unless (eq (char-before) ?\ )
- (insert " "))))
- (delete-region (point) (point-max))
- (save-excursion
- (insert old-input)
- (when (equal (char-before) ?\n)
- (delete-char -1)))
- (forward-char offset))))
-
- (defun cider-repl-closing-return ()
- "Evaluate the current input string after closing all open parenthesized or bracketed expressions."
- (interactive)
- (goto-char (point-max))
- (save-restriction
- (narrow-to-region cider-repl-input-start-mark (point))
- (let ((matching-delimiter nil))
- (while (ignore-errors
- (save-excursion
- (backward-up-list 1)
- (setq matching-delimiter (cdr (syntax-after (point)))))
- t)
- (insert-char matching-delimiter))))
- (cider-repl-return))
-
- (defun cider-repl-toggle-pretty-printing ()
- "Toggle pretty-printing in the REPL."
- (interactive)
- (setq cider-repl-use-pretty-printing (not cider-repl-use-pretty-printing))
- (message "Pretty printing in REPL %s."
- (if cider-repl-use-pretty-printing "enabled" "disabled")))
-
- (defun cider-repl-toggle-content-types ()
- "Toggle content-type rendering in the REPL."
- (interactive)
- (setq cider-repl-use-content-types (not cider-repl-use-content-types))
- (message "Content-type support in REPL %s."
- (if cider-repl-use-content-types "enabled" "disabled")))
-
- (defun cider-repl-switch-to-other ()
- "Switch between the Clojure and ClojureScript REPLs for the current project."
- (interactive)
- ;; FIXME: implement cycling as session can hold more than two REPLs
- (let* ((this-repl (cider-current-repl nil 'ensure))
- (other-repl (car (seq-remove (lambda (r) (eq r this-repl)) (cider-repls nil t)))))
- (if other-repl
- (switch-to-buffer other-repl)
- (user-error "No other REPL in current session (%s)"
- (car (sesman-current-session 'CIDER))))))
-
- (defvar cider-repl-clear-buffer-hook)
-
- (defun cider-repl--clear-region (start end)
- "Delete the output and its overlays between START and END."
- (mapc #'delete-overlay (overlays-in start end))
- (delete-region start end))
-
- (defun cider-repl-clear-buffer ()
- "Clear the currently visited REPL buffer completely.
- See also the related commands `cider-repl-clear-output' and
- `cider-find-and-clear-repl-output'."
- (interactive)
- (let ((inhibit-read-only t))
- (cider-repl--clear-region (point-min) cider-repl-prompt-start-mark)
- (cider-repl--clear-region cider-repl-output-start cider-repl-output-end)
- (when (< (point) cider-repl-input-start-mark)
- (goto-char cider-repl-input-start-mark))
- (recenter t))
- (run-hooks 'cider-repl-clear-buffer-hook))
-
- (defun cider-repl-clear-output (&optional clear-repl)
- "Delete the output inserted since the last input.
- With a prefix argument CLEAR-REPL it will clear the entire REPL buffer instead."
- (interactive "P")
- (if clear-repl
- (cider-repl-clear-buffer)
- (let ((inhibit-read-only t))
- (cider-repl--clear-region cider-repl-output-start cider-repl-output-end)
- (save-excursion
- (goto-char cider-repl-output-end)
- (insert-before-markers
- (propertize ";; output cleared\n" 'font-lock-face 'font-lock-comment-face))))))
-
- (defun cider-repl-clear-banners ()
- "Delete the REPL banners."
- (interactive)
- ;; TODO: Improve the boundaries detecting logic
- ;; probably it should be based on text properties
- ;; the current implemetation will clear warnings as well
- (let ((start (point-min))
- (end (save-excursion
- (goto-char (point-min))
- (cider-repl-next-prompt)
- (forward-line -1)
- (end-of-line)
- (point))))
- (when (< start end)
- (let ((inhibit-read-only t))
- (cider-repl--clear-region start (1+ end))))))
-
- (defun cider-repl-clear-help-banner ()
- "Delete the help REPL banner."
- (interactive)
- ;; TODO: Improve the boundaries detecting logic
- ;; probably it should be based on text properties
- (let ((start (save-excursion
- (goto-char (point-min))
- (search-forward ";; =")
- (beginning-of-line)
- (point)))
- (end (save-excursion
- (goto-char (point-min))
- (cider-repl-next-prompt)
- (search-backward ";; =")
- (end-of-line)
- (point))))
- (when (< start end)
- (let ((inhibit-read-only t))
- (cider-repl--clear-region start (1+ end))))))
-
- (defun cider-repl-switch-ns-handler (buffer)
- "Make an nREPL evaluation handler for the REPL BUFFER's ns switching."
- (nrepl-make-response-handler buffer
- (lambda (_buffer _value))
- (lambda (buffer out)
- (cider-repl-emit-stdout buffer out))
- (lambda (buffer err)
- (cider-repl-emit-stderr buffer err))
- (lambda (buffer)
- (cider-repl-emit-prompt buffer))))
-
- (defun cider-repl-set-ns (ns)
- "Switch the namespace of the REPL buffer to NS.
- If called from a cljc buffer act on both the Clojure and ClojureScript REPL
- if there are more than one REPL present. If invoked in a REPL buffer the
- command will prompt for the name of the namespace to switch to."
- (interactive (list (if (or (derived-mode-p 'cider-repl-mode)
- (null (cider-ns-form)))
- (completing-read "Switch to namespace: "
- (cider-sync-request:ns-list))
- (cider-current-ns))))
- (when (or (not ns) (equal ns ""))
- (user-error "No namespace selected"))
- (cider-map-repls :auto
- (lambda (connection)
- (cider-nrepl-request:eval (if cider-repl-require-ns-on-set
- (format "(do (require '%s) (in-ns '%s))" ns ns)
- (format "(in-ns '%s)" ns))
- (cider-repl-switch-ns-handler connection)))))
-
- ;;; Location References
-
- (defcustom cider-locref-regexp-alist
- '((stdout-stacktrace "[ \t]\\(at \\([^$(]+\\).*(\\([^:()]+\\):\\([0-9]+\\))\\)" 1 2 3 4)
- (aviso-stacktrace "^[ \t]*\\(\\([^$/ \t]+\\).*? +\\([^:]+\\): +\\([0-9]+\\)\\)" 1 2 3 4)
- (print-stacktrace "\\[\\([^][$ \t]+\\).* +\\([^ \t]+\\) +\\([0-9]+\\)\\]" 0 1 2 3)
- (timbre-log "\\(TRACE\\|INFO\\|DEBUG\\|WARN\\|ERROR\\) +\\(\\[\\([^:]+\\):\\([0-9]+\\)\\]\\)" 2 3 nil 4)
- (cljs-message "at line \\([0-9]+\\) +\\(.*\\)$" 0 nil 2 1)
- (warning "warning,? +\\(\\([^\n:]+\\):\\([0-9]+\\):[0-9]+\\)" 1 nil 2 3)
- (compilation ".*compiling:(\\([^\n:)]+\\):\\([0-9]+\\):[0-9]+)" 0 nil 1 2))
- "Alist holding regular expressions for inline location references.
- Each element in the alist has the form (NAME REGEXP HIGHLIGHT VAR FILE
- LINE), where NAME is the identifier of the regexp, REGEXP - regexp matching
- a location, HIGHLIGHT - sub-expression matching region to highlight on
- mouse-over, VAR - sub-expression giving Clojure VAR to look up. FILE is
- currently only used when VAR is nil and must be full resource path in that
- case."
- :type '(alist :key-type sexp)
- :group 'cider-repl
- :package-version '(cider. "0.16.0"))
-
- (defun cider--locref-at-point-1 (reg-list)
- "Workhorse for getting locref at point.
- REG-LIST is an entry in `cider-locref-regexp-alist'."
- (beginning-of-line)
- (when (re-search-forward (nth 1 reg-list) (point-at-eol) t)
- (let ((ix-highlight (or (nth 2 reg-list) 0))
- (ix-var (nth 3 reg-list))
- (ix-file (nth 4 reg-list))
- (ix-line (nth 5 reg-list)))
- (list
- :type (car reg-list)
- :highlight (cons (match-beginning ix-highlight) (match-end ix-highlight))
- :var (and ix-var
- (replace-regexp-in-string "_" "-"
- (match-string-no-properties ix-var)
- nil t))
- :file (and ix-file (match-string-no-properties ix-file))
- :line (and ix-line (string-to-number (match-string-no-properties ix-line)))))))
-
- (defun cider-locref-at-point (&optional pos)
- "Return a plist of components of the location reference at POS.
- Limit search to current line only and return nil if no location has been
- found. Returned keys are :type, :highlight, :var, :file, :line, where
- :highlight is a cons of positions, :var and :file are strings or nil, :line
- is a number. See `cider-locref-regexp-alist' for how to specify regexes
- for locref look up."
- (save-excursion
- (goto-char (or pos (point)))
- ;; Regexp lookup on long lines can result in significant hangs #2532. We
- ;; assume that lines longer than 300 don't contain source references.
- (when (< (- (point-at-eol) (point-at-bol)) 300)
- (seq-some (lambda (rl) (cider--locref-at-point-1 rl))
- cider-locref-regexp-alist))))
-
- (defun cider-jump-to-locref-at-point (&optional pos)
- "Identify location reference at POS and navigate to it.
- This function is used from help-echo property inside REPL buffers and uses
- regexes from `cider-locref-regexp-alist' to infer locations at point."
- (interactive)
- (if-let* ((loc (cider-locref-at-point pos)))
- (let* ((var (plist-get loc :var))
- (line (plist-get loc :line))
- (file (or
- ;; 1) retrieve from info middleware
- (when var
- (or (cider-sync-request:ns-path var)
- (nrepl-dict-get (cider-sync-request:info var) "file")))
- (when-let* ((file (plist-get loc :file)))
- ;; 2) file detected by the regexp
- (or
- (if (file-name-absolute-p file)
- file
- ;; when not absolute, expand within the current project
- (when-let* ((proj (clojure-project-dir)))
- (let ((path (expand-file-name file proj)))
- (when (file-exists-p path)
- path))))
- ;; 3) infer ns from the abbreviated path (common in
- ;; reflection warnings)
- (let ((ns (cider-path-to-ns file)))
- (cider-sync-request:ns-path ns)))))))
- (if file
- (cider--jump-to-loc-from-info (nrepl-dict "file" file "line" line) t)
- (error "No source location for %s" var)))
- (user-error "No location reference at point")))
-
- (defvar cider-locref-hoover-overlay
- (let ((o (make-overlay 1 1)))
- (overlay-put o 'category 'cider-error-hoover)
- ;; (overlay-put o 'face 'highlight)
- (overlay-put o 'pointer 'hand)
- (overlay-put o 'mouse-face 'highlight)
- (overlay-put o 'follow-link 'mouse)
- (overlay-put o 'keymap
- (let ((map (make-sparse-keymap)))
- (define-key map [return] 'cider-jump-to-locref-at-point)
- (define-key map [mouse-2] 'cider-jump-to-locref-at-point)
- map))
- o)
- "Overlay used during hoovering on location references in REPL buffers.
- One for all REPLs.")
-
- (defun cider-locref-help-echo (_win buffer pos)
- "Function for help-echo property in REPL buffers.
- WIN, BUFFER and POS are the window, buffer and point under mouse position."
- (with-current-buffer buffer
- (if-let* ((hl (plist-get (cider-locref-at-point pos) :highlight)))
- (move-overlay cider-locref-hoover-overlay (car hl) (cdr hl) buffer)
- (delete-overlay cider-locref-hoover-overlay))
- nil))
-
- ;;; History
-
- (defcustom cider-repl-wrap-history nil
- "T to wrap history around when the end is reached."
- :type 'boolean
- :group 'cider-repl)
-
- ;; These two vars contain the state of the last history search. We
- ;; only use them if `last-command' was `cider-repl--history-replace',
- ;; otherwise we reinitialize them.
-
- (defvar cider-repl-input-history-position -1
- "Newer items have smaller indices.")
-
- (defvar cider-repl-history-pattern nil
- "The regexp most recently used for finding input history.")
-
- (defun cider-repl--add-to-input-history (string)
- "Add STRING to the input history.
- Empty strings and duplicates are ignored."
- (unless (or (equal string "")
- (equal string (car cider-repl-input-history)))
- (push string cider-repl-input-history)
- (cl-incf cider-repl-input-history-items-added)))
-
- (defun cider-repl-delete-current-input ()
- "Delete all text after the prompt."
- (goto-char (point-max))
- (delete-region cider-repl-input-start-mark (point-max)))
-
- (defun cider-repl--replace-input (string)
- "Replace the current REPL input with STRING."
- (cider-repl-delete-current-input)
- (insert-and-inherit string))
-
- (defun cider-repl--position-in-history (start-pos direction regexp)
- "Return the position of the history item starting at START-POS.
- Search in DIRECTION for REGEXP.
- Return -1 resp the length of the history if no item matches."
- ;; Loop through the history list looking for a matching line
- (let* ((step (cl-ecase direction
- (forward -1)
- (backward 1)))
- (history cider-repl-input-history)
- (len (length history)))
- (cl-loop for pos = (+ start-pos step) then (+ pos step)
- if (< pos 0) return -1
- if (<= len pos) return len
- if (string-match-p regexp (nth pos history)) return pos)))
-
- (defun cider-repl--history-replace (direction &optional regexp)
- "Replace the current input with the next line in DIRECTION.
- DIRECTION is 'forward' or 'backward' (in the history list).
- If REGEXP is non-nil, only lines matching REGEXP are considered."
- (setq cider-repl-history-pattern regexp)
- (let* ((min-pos -1)
- (max-pos (length cider-repl-input-history))
- (pos0 (cond ((cider-history-search-in-progress-p)
- cider-repl-input-history-position)
- (t min-pos)))
- (pos (cider-repl--position-in-history pos0 direction (or regexp "")))
- (msg nil))
- (cond ((and (< min-pos pos) (< pos max-pos))
- (cider-repl--replace-input (nth pos cider-repl-input-history))
- (setq msg (format "History item: %d" pos)))
- ((not cider-repl-wrap-history)
- (setq msg (cond ((= pos min-pos) "End of history")
- ((= pos max-pos) "Beginning of history"))))
- (cider-repl-wrap-history
- (setq pos (if (= pos min-pos) max-pos min-pos))
- (setq msg "Wrapped history")))
- (when (or (<= pos min-pos) (<= max-pos pos))
- (when regexp
- (setq msg (concat msg "; no matching item"))))
- (message "%s%s" msg (cond ((not regexp) "")
- (t (format "; current regexp: %s" regexp))))
- (setq cider-repl-input-history-position pos)
- (setq this-command 'cider-repl--history-replace)))
-
- (defun cider-history-search-in-progress-p ()
- "Return t if a current history search is in progress."
- (eq last-command 'cider-repl--history-replace))
-
- (defun cider-terminate-history-search ()
- "Terminate the current history search."
- (setq last-command this-command))
-
- (defun cider-repl-previous-input ()
- "Cycle backwards through input history.
- If the `last-command' was a history navigation command use the
- same search pattern for this command.
- Otherwise use the current input as search pattern."
- (interactive)
- (cider-repl--history-replace 'backward (cider-repl-history-pattern t)))
-
- (defun cider-repl-next-input ()
- "Cycle forwards through input history.
- See `cider-previous-input'."
- (interactive)
- (cider-repl--history-replace 'forward (cider-repl-history-pattern t)))
-
- (defun cider-repl-forward-input ()
- "Cycle forwards through input history."
- (interactive)
- (cider-repl--history-replace 'forward (cider-repl-history-pattern)))
-
- (defun cider-repl-backward-input ()
- "Cycle backwards through input history."
- (interactive)
- (cider-repl--history-replace 'backward (cider-repl-history-pattern)))
-
- (defun cider-repl-previous-matching-input (regexp)
- "Find the previous input matching REGEXP."
- (interactive "sPrevious element matching (regexp): ")
- (cider-terminate-history-search)
- (cider-repl--history-replace 'backward regexp))
-
- (defun cider-repl-next-matching-input (regexp)
- "Find then next input matching REGEXP."
- (interactive "sNext element matching (regexp): ")
- (cider-terminate-history-search)
- (cider-repl--history-replace 'forward regexp))
-
- (defun cider-repl-history-pattern (&optional use-current-input)
- "Return the regexp for the navigation commands.
- If USE-CURRENT-INPUT is non-nil, use the current input."
- (cond ((cider-history-search-in-progress-p)
- cider-repl-history-pattern)
- (use-current-input
- (cl-assert (<= cider-repl-input-start-mark (point)))
- (let ((str (cider-repl--current-input t)))
- (cond ((string-match-p "^[ \n]*$" str) nil)
- (t (concat "^" (regexp-quote str))))))
- (t nil)))
-
- ;;; persistent history
- (defcustom cider-repl-history-size 500
- "The maximum number of items to keep in the REPL history."
- :type 'integer
- :safe #'integerp
- :group 'cider-repl)
-
- (defcustom cider-repl-history-file nil
- "File to save the persistent REPL history to."
- :type 'string
- :safe #'stringp
- :group 'cider-repl)
-
- (defun cider-repl--history-read-filename ()
- "Ask the user which file to use, defaulting `cider-repl-history-file'."
- (read-file-name "Use CIDER REPL history file: "
- cider-repl-history-file))
-
- (defun cider-repl--history-read (filename)
- "Read history from FILENAME and return it.
- It does not yet set the input history."
- (if (file-readable-p filename)
- (with-temp-buffer
- (insert-file-contents filename)
- (when (> (buffer-size (current-buffer)) 0)
- (read (current-buffer))))
- '()))
-
- (defun cider-repl-history-load (&optional filename)
- "Load history from FILENAME into current session.
- FILENAME defaults to the value of `cider-repl-history-file' but user
- defined filenames can be used to read special history files.
-
- The value of `cider-repl-input-history' is set by this function."
- (interactive (list (cider-repl--history-read-filename)))
- (let ((f (or filename cider-repl-history-file)))
- ;; TODO: probably need to set cider-repl-input-history-position as well.
- ;; in a fresh connection the newest item in the list is currently
- ;; not available. After sending one input, everything seems to work.
- (setq cider-repl-input-history (cider-repl--history-read f))))
-
- (defun cider-repl--history-write (filename)
- "Write history to FILENAME.
- Currently coding system for writing the contents is hardwired to
- utf-8-unix."
- (let* ((mhist (cider-repl--histories-merge cider-repl-input-history
- cider-repl-input-history-items-added
- (cider-repl--history-read filename)))
- ;; newest items are at the beginning of the list, thus 0
- (hist (cl-subseq mhist 0 (min (length mhist) cider-repl-history-size))))
- (unless (file-writable-p filename)
- (error (format "History file not writable: %s" filename)))
- (let ((print-length nil) (print-level nil))
- (with-temp-file filename
- ;; TODO: really set cs for output
- ;; TODO: does cs need to be customizable?
- (insert ";; -*- coding: utf-8-unix -*-\n")
- (insert ";; Automatically written history of CIDER REPL session\n")
- (insert ";; Edit at your own risk\n\n")
- (prin1 (mapcar #'substring-no-properties hist) (current-buffer))))))
-
- (defun cider-repl-history-save (&optional filename)
- "Save the current REPL input history to FILENAME.
- FILENAME defaults to the value of `cider-repl-history-file'."
- (interactive (list (cider-repl--history-read-filename)))
- (let* ((file (or filename cider-repl-history-file)))
- (cider-repl--history-write file)))
-
- (defun cider-repl-history-just-save ()
- "Just save the history to `cider-repl-history-file'.
- This function is meant to be used in hooks to avoid lambda
- constructs."
- (cider-repl-history-save cider-repl-history-file))
-
- ;; SLIME has different semantics and will not save any duplicates.
- ;; we keep track of how many items were added to the history in the
- ;; current session in `cider-repl--add-to-input-history' and merge only the
- ;; new items with the current history found in the file, which may
- ;; have been changed in the meantime by another session.
- (defun cider-repl--histories-merge (session-hist n-added-items file-hist)
- "Merge histories from SESSION-HIST adding N-ADDED-ITEMS into FILE-HIST."
- (append (cl-subseq session-hist 0 n-added-items)
- file-hist))
-
- ;;; REPL shortcuts
- (defcustom cider-repl-shortcut-dispatch-char ?\,
- "Character used to distinguish REPL commands from Lisp forms."
- :type '(character)
- :group 'cider-repl)
-
- (defvar cider-repl-shortcuts (make-hash-table :test 'equal))
-
- (defun cider-repl-add-shortcut (name handler)
- "Add a REPL shortcut command, defined by NAME and HANDLER."
- (puthash name handler cider-repl-shortcuts))
-
- (declare-function cider-toggle-trace-ns "cider-tracing")
- (declare-function cider-undef "cider-mode")
- (declare-function cider-browse-ns "cider-browse-ns")
- (declare-function cider-classpath "cider-classpath")
- (declare-function cider-repl-history "cider-repl-history")
- (declare-function cider-run "cider-mode")
- (declare-function cider-ns-refresh "cider-ns")
- (declare-function cider-ns-reload "cider-ns")
- (declare-function cider-find-var "cider-find")
- (declare-function cider-version "cider")
- (declare-function cider-test-run-loaded-tests "cider-test")
- (declare-function cider-test-run-project-tests "cider-test")
- (cider-repl-add-shortcut "clear-output" #'cider-repl-clear-output)
- (cider-repl-add-shortcut "clear" #'cider-repl-clear-buffer)
- (cider-repl-add-shortcut "clear-banners" #'cider-repl-clear-banners)
- (cider-repl-add-shortcut "clear-help-banner" #'cider-repl-clear-help-banner)
- (cider-repl-add-shortcut "ns" #'cider-repl-set-ns)
- (cider-repl-add-shortcut "toggle-pretty" #'cider-repl-toggle-pretty-printing)
- (cider-repl-add-shortcut "browse-ns" (lambda () (interactive) (cider-browse-ns (cider-current-ns))))
- (cider-repl-add-shortcut "classpath" #'cider-classpath)
- (cider-repl-add-shortcut "history" #'cider-repl-history)
- (cider-repl-add-shortcut "trace-ns" #'cider-toggle-trace-ns)
- (cider-repl-add-shortcut "undef" #'cider-undef)
- (cider-repl-add-shortcut "refresh" #'cider-ns-refresh)
- (cider-repl-add-shortcut "reload" #'cider-ns-reload)
- (cider-repl-add-shortcut "find-var" #'cider-find-var)
- (cider-repl-add-shortcut "doc" #'cider-doc)
- (cider-repl-add-shortcut "help" #'cider-repl-shortcuts-help)
- (cider-repl-add-shortcut "test-ns" #'cider-test-run-ns-tests)
- (cider-repl-add-shortcut "test-all" #'cider-test-run-loaded-tests)
- (cider-repl-add-shortcut "test-project" #'cider-test-run-project-tests)
- (cider-repl-add-shortcut "test-ns-with-filters" #'cider-test-run-ns-tests-with-filters)
- (cider-repl-add-shortcut "test-all-with-filters" (lambda () (interactive) (cider-test-run-loaded-tests 'prompt-for-filters)))
- (cider-repl-add-shortcut "test-project-with-filters" (lambda () (interactive) (cider-test-run-project-tests 'prompt-for-filters)))
- (cider-repl-add-shortcut "test-report" #'cider-test-show-report)
- (cider-repl-add-shortcut "run" #'cider-run)
- (cider-repl-add-shortcut "conn-info" #'cider-describe-connection)
- (cider-repl-add-shortcut "version" #'cider-version)
- (cider-repl-add-shortcut "require-repl-utils" #'cider-repl-require-repl-utils)
- ;; So many ways to quit :-)
- (cider-repl-add-shortcut "adios" #'cider-quit)
- (cider-repl-add-shortcut "sayonara" #'cider-quit)
- (cider-repl-add-shortcut "quit" #'cider-quit)
- (cider-repl-add-shortcut "restart" #'cider-restart)
-
- (defconst cider-repl-shortcuts-help-buffer "*CIDER REPL Shortcuts Help*")
-
- (defun cider-repl-shortcuts-help ()
- "Display a help buffer."
- (interactive)
- (ignore-errors (kill-buffer cider-repl-shortcuts-help-buffer))
- (with-current-buffer (get-buffer-create cider-repl-shortcuts-help-buffer)
- (insert "CIDER REPL shortcuts:\n\n")
- (maphash (lambda (k v) (insert (format "%s:\n\t%s\n" k v))) cider-repl-shortcuts)
- (goto-char (point-min))
- (help-mode)
- (display-buffer (current-buffer) t))
- (cider-repl-handle-shortcut)
- (current-buffer))
-
- (defun cider-repl--available-shortcuts ()
- "Return the available REPL shortcuts."
- (cider-util--hash-keys cider-repl-shortcuts))
-
- (defun cider-repl-handle-shortcut ()
- "Execute a REPL shortcut."
- (interactive)
- (if (> (point) cider-repl-input-start-mark)
- (insert (string cider-repl-shortcut-dispatch-char))
- (let ((command (completing-read "Command: "
- (cider-repl--available-shortcuts))))
- (if (not (equal command ""))
- (let ((command-func (gethash command cider-repl-shortcuts)))
- (if command-func
- (call-interactively command-func)
- (error "Unknown command %S. Available commands: %s"
- command-func
- (mapconcat 'identity (cider-repl--available-shortcuts) ", "))))
- (error "No command selected")))))
-
- ;;;;; CIDER REPL mode
- (defvar cider-repl-mode-hook nil
- "Hook executed when entering `cider-repl-mode'.")
-
- (defvar cider-repl-mode-syntax-table
- (copy-syntax-table clojure-mode-syntax-table))
-
- (declare-function cider-eval-last-sexp "cider-eval")
- (declare-function cider-toggle-trace-ns "cider-tracing")
- (declare-function cider-toggle-trace-var "cider-tracing")
- (declare-function cider-find-resource "cider-find")
- (declare-function cider-find-ns "cider-find")
- (declare-function cider-find-keyword "cider-find")
- (declare-function cider-find-var "cider-find")
- (declare-function cider-switch-to-last-clojure-buffer "cider-mode")
- (declare-function cider-macroexpand-1 "cider-macroexpansion")
- (declare-function cider-macroexpand-all "cider-macroexpansion")
- (declare-function cider-selector "cider-selector")
- (declare-function cider-jack-in-clj "cider")
- (declare-function cider-jack-in-cljs "cider")
- (declare-function cider-connect-clj "cider")
- (declare-function cider-connect-cljs "cider")
-
- (defvar cider-repl-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c C-d") 'cider-doc-map)
- (define-key map (kbd "C-c ,") 'cider-test-commands-map)
- (define-key map (kbd "C-c C-t") 'cider-test-commands-map)
- (define-key map (kbd "M-.") #'cider-find-var)
- (define-key map (kbd "C-c C-.") #'cider-find-ns)
- (define-key map (kbd "C-c C-:") #'cider-find-keyword)
- (define-key map (kbd "M-,") #'cider-pop-back)
- (define-key map (kbd "C-c M-.") #'cider-find-resource)
- (define-key map (kbd "RET") #'cider-repl-return)
- (define-key map (kbd "TAB") #'cider-repl-tab)
- (define-key map (kbd "C-<return>") #'cider-repl-closing-return)
- (define-key map (kbd "C-j") #'cider-repl-newline-and-indent)
- (define-key map (kbd "C-c C-o") #'cider-repl-clear-output)
- (define-key map (kbd "C-c M-n") #'cider-repl-set-ns)
- (define-key map (kbd "C-c C-u") #'cider-repl-kill-input)
- (define-key map (kbd "C-S-a") #'cider-repl-bol-mark)
- (define-key map [S-home] #'cider-repl-bol-mark)
- (define-key map (kbd "C-<up>") #'cider-repl-backward-input)
- (define-key map (kbd "C-<down>") #'cider-repl-forward-input)
- (define-key map (kbd "M-p") #'cider-repl-previous-input)
- (define-key map (kbd "M-n") #'cider-repl-next-input)
- (define-key map (kbd "M-r") #'cider-repl-previous-matching-input)
- (define-key map (kbd "M-s") #'cider-repl-next-matching-input)
- (define-key map (kbd "C-c C-n") #'cider-repl-next-prompt)
- (define-key map (kbd "C-c C-p") #'cider-repl-previous-prompt)
- (define-key map (kbd "C-c C-b") #'cider-interrupt)
- (define-key map (kbd "C-c C-c") #'cider-interrupt)
- (define-key map (kbd "C-c C-m") #'cider-macroexpand-1)
- (define-key map (kbd "C-c M-m") #'cider-macroexpand-all)
- (define-key map (kbd "C-c C-s") #'sesman-map)
- (define-key map (kbd "C-c C-z") #'cider-switch-to-last-clojure-buffer)
- (define-key map (kbd "C-c M-o") #'cider-repl-switch-to-other)
- (define-key map (kbd "C-c M-s") #'cider-selector)
- (define-key map (kbd "C-c M-d") #'cider-describe-connection)
- (define-key map (kbd "C-c C-q") #'cider-quit)
- (define-key map (kbd "C-c M-r") #'cider-restart)
- (define-key map (kbd "C-c M-i") #'cider-inspect)
- (define-key map (kbd "C-c M-p") #'cider-repl-history)
- (define-key map (kbd "C-c M-t v") #'cider-toggle-trace-var)
- (define-key map (kbd "C-c M-t n") #'cider-toggle-trace-ns)
- (define-key map (kbd "C-c C-x") 'cider-start-map)
- (define-key map (kbd "C-x C-e") #'cider-eval-last-sexp)
- (define-key map (kbd "C-c C-r") 'clojure-refactor-map)
- (define-key map (kbd "C-c C-v") 'cider-eval-commands-map)
- (define-key map (kbd "C-c M-j") #'cider-jack-in-clj)
- (define-key map (kbd "C-c M-J") #'cider-jack-in-cljs)
- (define-key map (kbd "C-c M-c") #'cider-connect-clj)
- (define-key map (kbd "C-c M-C") #'cider-connect-cljs)
-
- (define-key map (string cider-repl-shortcut-dispatch-char) #'cider-repl-handle-shortcut)
- (easy-menu-define cider-repl-mode-menu map
- "Menu for CIDER's REPL mode"
- `("REPL"
- ["Complete symbol" complete-symbol]
- "--"
- ,cider-doc-menu
- "--"
- ("Find"
- ["Find definition" cider-find-var]
- ["Find namespace" cider-find-ns]
- ["Find resource" cider-find-resource]
- ["Find keyword" cider-find-keyword]
- ["Go back" cider-pop-back])
- "--"
- ["Switch to Clojure buffer" cider-switch-to-last-clojure-buffer]
- ["Switch to other REPL" cider-repl-switch-to-other]
- "--"
- ("Macroexpand"
- ["Macroexpand-1" cider-macroexpand-1]
- ["Macroexpand-all" cider-macroexpand-all])
- "--"
- ,cider-test-menu
- "--"
- ["Run project (-main function)" cider-run]
- ["Inspect" cider-inspect]
- ["Toggle var tracing" cider-toggle-trace-var]
- ["Toggle ns tracing" cider-toggle-trace-ns]
- ["Refresh loaded code" cider-ns-refresh]
- "--"
- ["Set REPL ns" cider-repl-set-ns]
- ["Toggle pretty printing" cider-repl-toggle-pretty-printing]
- ["Require REPL utils" cider-repl-require-repl-utils]
- "--"
- ["Browse classpath" cider-classpath]
- ["Browse classpath entry" cider-open-classpath-entry]
- ["Browse namespace" cider-browse-ns]
- ["Browse all namespaces" cider-browse-ns-all]
- ["Browse spec" cider-browse-spec]
- ["Browse all specs" cider-browse-spec-all]
- "--"
- ["Next prompt" cider-repl-next-prompt]
- ["Previous prompt" cider-repl-previous-prompt]
- ["Clear output" cider-repl-clear-output]
- ["Clear buffer" cider-repl-clear-buffer]
- ["Clear banners" cider-repl-clear-banners]
- ["Clear help banner" cider-repl-clear-help-banner]
- ["Kill input" cider-repl-kill-input]
- "--"
- ["Interrupt evaluation" cider-interrupt]
- "--"
- ["Connection info" cider-describe-connection]
- "--"
- ["Close ancillary buffers" cider-close-ancillary-buffers]
- ["Quit" cider-quit]
- ["Restart" cider-restart]
- "--"
- ["Clojure Cheatsheet" cider-cheatsheet]
- "--"
- ["A sip of CIDER" cider-drink-a-sip]
- ["View manual online" cider-view-manual]
- ["View refcard online" cider-view-refcard]
- ["Report a bug" cider-report-bug]
- ["Version info" cider-version]))
- map))
-
- (sesman-install-menu cider-repl-mode-map)
-
- (defun cider-repl-wrap-fontify-function (func)
- "Return a function that will call FUNC narrowed to input region."
- (lambda (beg end &rest rest)
- (when (and cider-repl-input-start-mark
- (> end cider-repl-input-start-mark))
- (save-restriction
- (narrow-to-region cider-repl-input-start-mark (point-max))
- (let ((font-lock-dont-widen t))
- (apply func (max beg cider-repl-input-start-mark) end rest))))))
-
- (declare-function cider-complete-at-point "cider-completion")
- (defvar cider--static-font-lock-keywords)
-
- (define-derived-mode cider-repl-mode fundamental-mode "REPL"
- "Major mode for Clojure REPL interactions.
-
- \\{cider-repl-mode-map}"
- (clojure-mode-variables)
- (clojure-font-lock-setup)
- (font-lock-add-keywords nil cider--static-font-lock-keywords)
- (setq-local sesman-system 'CIDER)
- (setq-local font-lock-fontify-region-function
- (cider-repl-wrap-fontify-function font-lock-fontify-region-function))
- (setq-local font-lock-unfontify-region-function
- (cider-repl-wrap-fontify-function font-lock-unfontify-region-function))
- (set-syntax-table cider-repl-mode-syntax-table)
- (cider-eldoc-setup)
- ;; At the REPL, we define beginning-of-defun and end-of-defun to be
- ;; the start of the previous prompt or next prompt respectively.
- ;; Notice the interplay with `cider-repl-beginning-of-defun'.
- (setq-local beginning-of-defun-function #'cider-repl-mode-beginning-of-defun)
- (setq-local end-of-defun-function #'cider-repl-mode-end-of-defun)
- (setq-local prettify-symbols-alist clojure--prettify-symbols-alist)
- ;; apply dir-local variables to REPL buffers
- (hack-dir-local-variables-non-file-buffer)
- (when cider-repl-history-file
- (cider-repl-history-load cider-repl-history-file)
- (add-hook 'kill-buffer-hook #'cider-repl-history-just-save t t)
- (add-hook 'kill-emacs-hook #'cider-repl-history-just-save))
- (add-hook 'completion-at-point-functions #'cider-complete-at-point nil t)
- (add-hook 'paredit-mode-hook (lambda () (clojure-paredit-setup cider-repl-mode-map))))
-
- (provide 'cider-repl)
-
- ;;; cider-repl.el ends here
|