diff --git a/CHANGELOG.md b/CHANGELOG.md
index dac67f4dd..c30493a15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
* Add an option `cider-inspector-fill-frame` to control whether the cider inspector window fills its frame.
* [#1893](https://github.com/clojure-emacs/cider/issues/1893): Add negative prefix argument to `cider-refresh` to inhibit invoking of cider-refresh-functions
* [#1776](https://github.com/clojure-emacs/cider/issues/1776): Add new customization variable `cider-test-defining-forms` allowing new test defining forms to be recognized.
+* [#1860](https://github.com/clojure-emacs/cider/issues/1860): Add `cider-repl-history` to browse the REPL input history and insert elements from it into the REPL buffer.
### Changes
diff --git a/cider-mode.el b/cider-mode.el
index 60f85b899..51e61d89d 100644
--- a/cider-mode.el
+++ b/cider-mode.el
@@ -274,6 +274,7 @@ Configure `cider-cljs-lein-repl' to change the ClojureScript REPL to use."]
("Browse"
["Browse namespace" cider-browse-ns]
["Browse all namespaces" cider-browse-ns-all]
+ ["Browse REPL input history" cider-repl-history]
["Browse classpath" cider-classpath]
["Browse classpath entry" cider-open-classpath-entry]))
"Menu for CIDER interactions.")
diff --git a/cider-repl-history.el b/cider-repl-history.el
new file mode 100644
index 000000000..3f6242ed6
--- /dev/null
+++ b/cider-repl-history.el
@@ -0,0 +1,729 @@
+;;; cider-repl-history.el --- REPL input history browser
+
+;; Copyright (c) 2017 John Valente and browse-kill-ring authors
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see .
+
+;; This file is not part of GNU Emacs.
+
+;; Based heavily on browse-kill-ring
+;; https://github.com/browse-kill-ring/browse-kill-ring
+
+;;; Commentary:
+
+;; REPL input history browser for CIDER.
+
+;; Allows you to browse the full input history for your REPL buffer, and
+;; insert previous commands at the prompt.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'cider-compat)
+(require 'cider-popup)
+(require 'clojure-mode)
+(require 'derived)
+(require 'pulse)
+
+(defconst cider-repl-history-buffer "*cider-repl-history*")
+(add-to-list 'cider-ancillary-buffers cider-repl-history-buffer)
+
+(defgroup cider-repl-history nil
+ "A package for browsing and inserting the items in the CIDER command history."
+ :prefix "cider-repl-history-"
+ :group 'cider)
+
+(defvar cider-repl-history-display-styles
+ '((separated . cider-repl-history-insert-as-separated)
+ (one-line . cider-repl-history-insert-as-one-line)))
+
+(defcustom cider-repl-history-display-style 'separated
+ "How to display the CIDER command history items.
+
+If `one-line', then replace newlines with \"\\n\" for display.
+
+If `separated', then display `cider-repl-history-separator' between
+entries."
+ :type '(choice (const :tag "One line" one-line)
+ (const :tag "Separated" separated))
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-quit-action 'quit-window
+ "What action to take when `cider-repl-history-quit' is called.
+
+If `bury-buffer', then simply bury the *cider-repl-history* buffer, but keep
+the window.
+
+If `bury-and-delete-window', then bury the buffer, and (if there is
+more than one window) delete the window.
+
+If `delete-and-restore', then restore the window configuration to what it was
+before `cider-repl-history' was called, and kill the *cider-repl-history* buffer.
+
+If `quit-window', then restore the window configuration to what
+it was before `cider-repl-history' was called, and bury *cider-repl-history*.
+This is the default.
+
+If `kill-and-delete-window', then kill the *cider-repl-history* buffer, and
+delete the window on close.
+
+Otherwise, it should be a function to call."
+ ;; Note, if you use one of the non-"delete" options, after you "quit",
+ ;; the *cider-repl-history* buffer is still available. If you are using
+ ;; `cider-repl-history-show-preview', and you switch to *cider-repl-history* (i.e.,
+ ;; with C-x b), it will not give the preview unless and until you "update"
+ ;; the *cider-repl-history* buffer.
+ ;;
+ ;; This really should not be an issue, because there's no reason to "switch"
+ ;; back to the buffer. If you want to get it back, you can just do C-c M-p
+ ;; from the REPL buffer.
+
+ ;; If you get in this situation and find it annoying, you can either disable
+ ;; the preview, or set `cider-repl-history-quit-action' to 'delete-and-restore.
+ ;; Then you will simply not have the *cider-repl-history* buffer after you quit,
+ ;; and it won't be an issue.
+
+ :type '(choice (const :tag "Bury buffer"
+ :value bury-buffer)
+ (const :tag "Bury buffer and delete window"
+ :value bury-and-delete-window)
+ (const :tag "Delete window"
+ :value delete-and-restore)
+ (const :tag "Save and restore"
+ :value quit-window)
+ (const :tag "Kill buffer and delete window"
+ :value kill-and-delete-window)
+ function)
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-resize-window nil
+ "Whether to resize the `cider-repl-history' window to fit its contents.
+Value is either t, meaning yes, or a cons pair of integers,
+ (MAXIMUM . MINIMUM) for the size of the window. MAXIMUM defaults to
+the window size chosen by `pop-to-buffer'; MINIMUM defaults to
+`window-min-height'."
+ :type '(choice (const :tag "No" nil)
+ (const :tag "Yes" t)
+ (cons (integer :tag "Maximum") (integer :tag "Minimum")))
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-separator ";;;;;;;;;;"
+ "The string separating entries in the `separated' style.
+See `cider-repl-history-display-style'."
+ ;; The (default) separator is a Clojure comment, to preserve fontification
+ ;; in the buffer.
+ :type 'string
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-recenter nil
+ "If non-nil, then always keep the current entry at the top of the window."
+ :type 'boolean
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-highlight-current-entry nil
+ "If non-nil, highlight the currently selected command history entry."
+ :type 'boolean
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-highlight-inserted-item nil
+ "If non-nil, then temporarily highlight the inserted command history entry.
+The value selected controls how the inserted item is highlighted,
+possible values are `solid' (highlight the inserted text for a
+fixed period of time), or `pulse' (fade out the highlighting gradually).
+Setting this variable to the value t will select the default
+highlighting style, which currently `pulse'.
+
+The variable `cider-repl-history-inserted-item-face' contains the
+face used for highlighting."
+ :type '(choice (const nil) (const t) (const solid) (const pulse))
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-separator-face 'bold
+ "The face in which to highlight the `cider-repl-history-separator'."
+ :type 'face
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-current-entry-face 'highlight
+ "The face in which to highlight the command history current entry."
+ :type 'face
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-inserted-item-face 'highlight
+ "The face in which to highlight the inserted item."
+ :type 'face
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-maximum-display-length nil
+ "Whether or not to limit the length of displayed items.
+
+If this variable is an integer, the display of the command history will be
+limited to that many characters.
+Setting this variable to nil means no limit."
+ :type '(choice (const :tag "None" nil)
+ integer)
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-display-duplicates t
+ "If non-nil, then display duplicate items in the command history."
+ :type 'boolean
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-display-duplicate-highest t
+ "When `cider-repl-history-display-duplicates' is nil, then display highest (most recent) duplicate items in the command history."
+ :type 'boolean
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-text-properties nil
+ "If non-nil, maintain text properties of the command history items."
+ :type 'boolean
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-hook nil
+ "A list of functions to call after `cider-repl-history'."
+ :type 'hook
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defcustom cider-repl-history-show-preview nil
+ "If non-nil, show a preview of the inserted text in the REPL buffer.
+
+The REPL buffer would show a preview of what the buffer would look like
+if the item under point were inserted."
+
+ :type 'boolean
+ :group 'cider-repl-history
+ :package-version '(cider . "0.15.0"))
+
+(defvar cider-repl-history-repl-window nil
+ "The window in which chosen command history data will be inserted.
+It is probably not a good idea to set this variable directly; simply
+call `cider-repl-history' again.")
+
+(defvar cider-repl-history-repl-buffer nil
+ "The buffer in which chosen command history data will be inserted.
+It is probably not a good idea to set this variable directly; simply
+call `cider-repl-history' again.")
+
+(defvar cider-repl-history-preview-overlay nil
+ "The overlay used to preview what would happen if the user inserted the given text.")
+
+(defvar cider-repl-history-previous-overlay nil
+ "Previous overlay within *cider-repl-history* buffer.")
+
+
+(defun cider-repl-history-get-history ()
+ "Function to retrieve history from the REPL buffer."
+ (if cider-repl-history-repl-buffer
+ (buffer-local-value
+ 'cider-repl-input-history
+ cider-repl-history-repl-buffer)
+ (error "Variable `cider-repl-history-repl-buffer' not bound to a buffer")))
+
+(defun cider-repl-history-resize-window ()
+ "If variable `cider-repl-history-resize-window' is non-nil, resize the *cider-repl-history* window."
+ (when cider-repl-history-resize-window
+ (apply #'fit-window-to-buffer (selected-window)
+ (if (consp cider-repl-history-resize-window)
+ (list (car cider-repl-history-resize-window)
+ (or (cdr cider-repl-history-resize-window)
+ window-min-height))
+ (list nil window-min-height)))))
+
+(defun cider-repl-history-read-regexp (msg use-default-p)
+ "Get a regular expression from the user, prompting with MSG; previous entry is default if USE-DEFAULT-P."
+ (let* ((default (car regexp-history))
+ (input
+ (read-from-minibuffer
+ (if (and default use-default-p)
+ (format "%s for regexp (default `%s'): "
+ msg
+ default)
+ (format "%s (regexp): " msg))
+ nil
+ nil
+ nil
+ 'regexp-history
+ (if use-default-p nil default))))
+ (if (equal input "")
+ (if use-default-p default nil)
+ input)))
+
+(defun cider-repl-history-clear-preview ()
+ "Clear the preview, if one is present."
+ (interactive)
+ (when cider-repl-history-preview-overlay
+ (cl-assert (overlayp cider-repl-history-preview-overlay))
+ (delete-overlay cider-repl-history-preview-overlay)))
+
+(defun cider-repl-history-cleanup-on-exit ()
+ "Function called when the user is finished with `cider-repl-history'.
+This function performs any cleanup that is required when the user
+has finished interacting with the *cider-repl-history* buffer. For now
+the only cleanup performed is to remove the preview overlay, if
+it's turned on."
+ (cider-repl-history-clear-preview))
+
+(defun cider-repl-history-quit ()
+ "Take the action specified by `cider-repl-history-quit-action'."
+ (interactive)
+ (cider-repl-history-cleanup-on-exit)
+ (pcase cider-repl-history-quit-action
+ (`delete-and-restore
+ (quit-restore-window (selected-window) 'kill))
+ (`quit-window
+ (quit-window))
+ (`kill-and-delete-window
+ (kill-buffer (current-buffer))
+ (unless (= (count-windows) 1)
+ (delete-window)))
+ (`bury-and-delete-window
+ (bury-buffer)
+ (unless (= (count-windows) 1)
+ (delete-window)))
+ (_
+ (funcall cider-repl-history-quit-action))))
+
+(defun cider-repl-history-preview-overlay-setup (orig-buf)
+ "Setup the preview overlay in ORIG-BUF."
+ (when cider-repl-history-show-preview
+ (with-current-buffer orig-buf
+ (let* ((will-replace (region-active-p))
+ (start (if will-replace
+ (min (point) (mark))
+ (point)))
+ (end (if will-replace
+ (max (point) (mark))
+ (point))))
+ (cider-repl-history-clear-preview)
+ (setq cider-repl-history-preview-overlay
+ (make-overlay start end orig-buf))
+ (overlay-put cider-repl-history-preview-overlay
+ 'invisible t)))))
+
+(defun cider-repl-history-highlight-inserted (start end)
+ "Insert the text between START and END."
+ (pcase cider-repl-history-highlight-inserted-item
+ ((or `pulse `t)
+ (let ((pulse-delay .05) (pulse-iterations 10))
+ (with-no-warnings
+ (pulse-momentary-highlight-region
+ start end cider-repl-history-inserted-item-face))))
+ (`solid
+ (let ((o (make-overlay start end)))
+ (overlay-put o 'face cider-repl-history-inserted-item-face)
+ (sit-for 0.5)
+ (delete-overlay o)))))
+
+(defun cider-repl-history-insert-and-highlight (str)
+ "Helper function to insert STR at point, highlighting it if appropriate."
+ (let ((before-insert (point)))
+ (let (deactivate-mark)
+ (insert-for-yank str))
+ (cider-repl-history-highlight-inserted
+ before-insert
+ (point))))
+
+(defun cider-repl-history-target-overlay-at (position &optional no-error)
+ "Return overlay at POSITION that has property `cider-repl-history-target'.
+If no such overlay, raise an error unless NO-ERROR is true, in which
+case retun nil."
+ (let ((ovs (overlays-at (point))))
+ (catch 'cider-repl-history-target-overlay-at
+ (dolist (ov ovs)
+ (when (overlay-get ov 'cider-repl-history-target)
+ (throw 'cider-repl-history-target-overlay-at ov)))
+ (unless no-error
+ (error "No CIDER history item here")))))
+
+(defun cider-repl-history-current-string (pt &optional no-error)
+ "Find the string to insert into the REPL by looking for the overlay at PT; might error unless NO-ERROR set."
+ (let ((o (cider-repl-history-target-overlay-at pt t)))
+ (if o
+ (overlay-get o 'cider-repl-history-target)
+ (unless no-error
+ (error "No CIDER history item in this buffer")))))
+
+(defun cider-repl-history-do-insert (buf pt)
+ "Helper function to insert text from BUF at PT into the REPL buffer and kill *cider-repl-history*."
+ ;; Note: as mentioned at the top, this file is based on browse-kill-ring,
+ ;; which has numerous insertion options. The functionality of
+ ;; browse-kill-ring allows users to insert at point, and move point to the end
+ ;; of the inserted text; or insert at the beginning or end of the buffer,
+ ;; while leaving point alone. And each of these had the option of leaving the
+ ;; history buffer in place, or getting rid of it. That was appropriate for a
+ ;; generic paste tool, but for inserting a previous command into an
+ ;; interpreter, I felt the only useful option would be inserting it at the end
+ ;; and quitting the history buffer, so that is all that's provided.
+ (let ((str (cider-repl-history-current-string pt)))
+ (cider-repl-history-quit)
+ (with-selected-window cider-repl-history-repl-window
+ (with-current-buffer cider-repl-history-repl-buffer
+ (let ((max (point-max)))
+ (if (= max (point))
+ (cider-repl-history-insert-and-highlight str)
+ (save-excursion
+ (goto-char max)
+ (cider-repl-history-insert-and-highlight str))))))))
+
+(defun cider-repl-history-insert-and-quit ()
+ "Insert the item into the REPL buffer, and close the *cider-repl-history* buffer.
+
+The text is always inserted at the very bottom of the REPL buffer. If your
+cursor is already at the bottom, it is advanced to the end of the inserted
+text. If your cursor is somewhere else, the cursor is not moved, but the
+text is still inserted at the end."
+ (interactive)
+ (cider-repl-history-do-insert (current-buffer) (point)))
+
+(defun cider-repl-history-mouse-insert (e)
+ "Insert the item at E into the REPL buffer, and close the *cider-repl-history*.
+
+The text is always inserted at the very bottom of the REPL buffer. If your
+cursor is already at the bottom, it is advanced to the end of the inserted
+text. If your cursor is somewhere else, the cursor is not moved, but the
+text is still inserted at the end."
+ (interactive "e")
+ (let* ((data (save-excursion
+ (mouse-set-point e)
+ (cons (current-buffer) (point))))
+ (buf (car data))
+ (pt (cdr data)))
+ (cider-repl-history-do-insert buf pt)))
+
+(defun cider-repl-history-clear-highlighed-entry ()
+ "Clear the highlighted entry, when one exists."
+ (when cider-repl-history-previous-overlay
+ (cl-assert (overlayp cider-repl-history-previous-overlay)
+ t "not an overlay")
+ (overlay-put cider-repl-history-previous-overlay 'face nil)))
+
+(defun cider-repl-history-update-highlighed-entry ()
+ "Update highlighted entry, when feature is turned on."
+ (when cider-repl-history-highlight-current-entry
+ (if-let ((current-overlay (cider-repl-history-target-overlay-at (point) t)))
+ (unless (equal cider-repl-history-previous-overlay current-overlay)
+ ;; We've changed overlay. Clear current highlighting,
+ ;; and highlight the new overlay.
+ (cl-assert (overlay-get current-overlay 'cider-repl-history-target) t)
+ (cider-repl-history-clear-highlighed-entry)
+ (setq cider-repl-history-previous-overlay current-overlay)
+ (overlay-put current-overlay 'face
+ cider-repl-history-current-entry-face))
+ ;; No overlay at point. Just clear all current highlighting.
+ (cider-repl-history-clear-highlighed-entry))))
+
+(defun cider-repl-history-forward (&optional arg)
+ "Move forward by ARG command history entries."
+ (interactive "p")
+ (beginning-of-line)
+ (while (not (zerop arg))
+ (let ((o (cider-repl-history-target-overlay-at (point) t)))
+ (cond
+ ((>= arg 0)
+ (setq arg (1- arg))
+ ;; We're on a cider-repl-history overlay, skip to the end of it.
+ (when o
+ (goto-char (overlay-end o))
+ (setq o nil))
+ (while (not (or o (eobp)))
+ (goto-char (next-overlay-change (point)))
+ (setq o (cider-repl-history-target-overlay-at (point) t))))
+ (t
+ (setq arg (1+ arg))
+ (when o
+ (goto-char (overlay-start o))
+ (setq o nil))
+ (while (not (or o (bobp)))
+ (goto-char (previous-overlay-change (point)))
+ (setq o (cider-repl-history-target-overlay-at (point) t)))))))
+ (when cider-repl-history-recenter
+ (recenter 1)))
+
+(defun cider-repl-history-previous (&optional arg)
+ "Move backward by ARG command history entries."
+ (interactive "p")
+ (cider-repl-history-forward (- arg)))
+
+(defun cider-repl-history-search-forward (regexp &optional backwards)
+ "Move to the next command history entry matching REGEXP from point.
+If optional arg BACKWARDS is non-nil, move to the previous matching
+entry."
+ (interactive
+ (list (cider-repl-history-read-regexp "Search forward" t)
+ current-prefix-arg))
+ (let ((orig (point)))
+ (cider-repl-history-forward (if backwards -1 1))
+ (let ((over (cider-repl-history-target-overlay-at (point) t)))
+ (while (and over
+ (not (if backwards (bobp) (eobp)))
+ (not (string-match regexp
+ (overlay-get over
+ 'cider-repl-history-target))))
+ (cider-repl-history-forward (if backwards -1 1))
+ (setq over (cider-repl-history-target-overlay-at (point) t)))
+ (unless (and over
+ (string-match regexp
+ (overlay-get over
+ 'cider-repl-history-target)))
+ (goto-char orig)
+ (message "No more command history entries matching %s" regexp)))))
+
+(defun cider-repl-history-search-backward (regexp)
+ "Move to the previous command history entry matching REGEXP from point."
+ (interactive
+ (list (cider-repl-history-read-regexp "Search backward" t)))
+ (cider-repl-history-search-forward regexp t))
+
+(defun cider-repl-history-elide (str)
+ "If STR is too long, abbreviate it with an ellipsis; otherwise, return it unchanged."
+ (if (and cider-repl-history-maximum-display-length
+ (> (length str)
+ cider-repl-history-maximum-display-length))
+ (concat (substring str 0 (- cider-repl-history-maximum-display-length 3))
+ (propertize "..." 'cider-repl-history-extra t))
+ str))
+
+(defmacro cider-repl-history-add-overlays-for (item &rest body)
+ "Add overlays for ITEM, and execute BODY."
+ (let ((beg (cl-gensym "cider-repl-history-add-overlays-"))
+ (end (cl-gensym "cider-repl-history-add-overlays-")))
+ `(let ((,beg (point))
+ (,end
+ (progn
+ ,@body
+ (point))))
+ (let ((o (make-overlay ,beg ,end)))
+ (overlay-put o 'cider-repl-history-target ,item)
+ (overlay-put o 'mouse-face 'highlight)))))
+
+(defun cider-repl-history-insert-as-separated (items)
+ "Insert ITEMS into the current buffer, with separators between items."
+ (while items
+ (let* ((origitem (car items))
+ (item (cider-repl-history-elide origitem))
+ (len (length item)))
+ (cider-repl-history-add-overlays-for origitem (insert item))
+ ;; When the command history has items with read-only text property at
+ ;; **the end of** string, cider-repl-history-setup fails with error
+ ;; `Text is read-only'. So inhibit-read-only here.
+ ;; See http://bugs.debian.org/225082
+ (let ((inhibit-read-only t))
+ (insert "\n")
+ (when (cdr items)
+ (insert (propertize cider-repl-history-separator
+ 'cider-repl-history-extra t
+ 'cider-repl-history-separator t))
+ (insert "\n"))))
+ (setq items (cdr items))))
+
+(defun cider-repl-history-insert-as-one-line (items)
+ "Insert ITEMS into the current buffer, formatting each item as a single line.
+
+An explicit newline character will replace newlines so that the text retains its
+spacing when it's actually inserted into the REPL buffer."
+ (dolist (item items)
+ (cider-repl-history-add-overlays-for
+ item
+ (let* ((item (cider-repl-history-elide item))
+ (len (length item))
+ (start 0)
+ (newl (propertize "\\n" 'cider-repl-history-extra t)))
+ (while (and (< start len)
+ (string-match "\n" item start))
+ (insert (substring item start (match-beginning 0))
+ newl)
+ (setq start (match-end 0)))
+ (insert (substring item start len))))
+ (insert "\n")))
+
+(defun cider-repl-history-preview-update-text (preview-text)
+ "Update `cider-repl-history-preview-overlay' to show `PREVIEW-TEXT`."
+ ;; If preview-text is nil, replacement should be nil too.
+ (cl-assert (overlayp cider-repl-history-preview-overlay))
+ (let ((replacement (when preview-text
+ (propertize preview-text 'face 'highlight))))
+ (overlay-put cider-repl-history-preview-overlay
+ 'before-string replacement)))
+
+(defun cider-repl-history-preview-update-by-position (&optional pt)
+ "Update `cider-repl-history-preview-overlay' to match item at PT.
+
+This function is called whenever the selection in the *cider-repl-history*
+buffer is adjusted, the `cider-repl-history-preview-overlay'
+is udpated to preview the text of the selection at PT (or the
+current point if not specified)."
+ (let ((new-text (cider-repl-history-current-string
+ (or pt (point)) t)))
+ (cider-repl-history-preview-update-text new-text)))
+
+(defun cider-repl-history-undo-other-window ()
+ "Undo the most recent change in the other window's buffer.
+You most likely want to use this command for undoing an insertion of
+text from the *cider-repl-history* buffer."
+ (interactive)
+ (with-current-buffer cider-repl-history-repl-buffer
+ (undo)))
+
+(defun cider-repl-history-setup (repl-win repl-buf history-buf &optional regexp)
+ "Setup: REPL-WIN and REPL-BUF are where to insert commands, HISTORY-BUF is the history, and optional arg REGEXP is a filter."
+ (cider-repl-history-preview-overlay-setup repl-buf)
+ (with-current-buffer history-buf
+ (unwind-protect
+ (progn
+ (cider-repl-history-mode)
+ (setq buffer-read-only nil)
+ (when (eq 'one-line cider-repl-history-display-style)
+ (setq truncate-lines t))
+ (let ((inhibit-read-only t))
+ (erase-buffer))
+ (setq cider-repl-history-repl-buffer repl-buf)
+ (setq cider-repl-history-repl-window repl-win)
+ (let* ((cider-repl-history-maximum-display-length
+ (if (and cider-repl-history-maximum-display-length
+ (<= cider-repl-history-maximum-display-length 3))
+ 4
+ cider-repl-history-maximum-display-length))
+ (cider-command-history (cider-repl-history-get-history))
+ (items (mapcar
+ (if cider-repl-history-text-properties
+ #'copy-sequence
+ #'substring-no-properties)
+ cider-command-history)))
+ (when (not cider-repl-history-display-duplicates)
+ ;; display highest or lowest duplicate.
+ ;; if `cider-repl-history-display-duplicate-highest' is t,
+ ;; display highest (most recent) duplicate.
+ (cl-delete-duplicates
+ items
+ :test #'equal
+ :from-end cider-repl-history-display-duplicate-highest))
+ (when (stringp regexp)
+ (setq items (delq nil
+ (mapcar
+ #'(lambda (item)
+ (when (string-match regexp item)
+ item))
+ items))))
+ (funcall (or (cdr (assq cider-repl-history-display-style
+ cider-repl-history-display-styles))
+ (error "Invalid `cider-repl-history-display-style': %s"
+ cider-repl-history-display-style))
+ items)
+ (when cider-repl-history-show-preview
+ (cider-repl-history-preview-update-by-position (point-min))
+ ;; Local post-command-hook, only happens in *cider-repl-history*
+ (add-hook 'post-command-hook
+ 'cider-repl-history-preview-update-by-position
+ nil t)
+ (add-hook 'kill-buffer-hook
+ 'cider-repl-history-cleanup-on-exit
+ nil t))
+ (when cider-repl-history-highlight-current-entry
+ (add-hook 'post-command-hook
+ 'cider-repl-history-update-highlighed-entry
+ nil t))
+ (message
+ (let ((entry (if (= 1 (length cider-command-history))
+ "entry"
+ "entries")))
+ (concat
+ (if (and (not regexp)
+ cider-repl-history-display-duplicates)
+ (format "%s %s in the command history."
+ (length cider-command-history) entry)
+ (format "%s (of %s) %s in the command history shown."
+ (length items) (length cider-command-history) entry))
+ (substitute-command-keys
+ (concat " Type \\[cider-repl-history-quit] to quit. "
+ "\\[describe-mode] for help.")))))
+ (set-buffer-modified-p nil)
+ (goto-char (point-min))
+ (cider-repl-history-forward 0)
+ (setq mode-name (if regexp
+ (concat "History [" regexp "]")
+ "History"))
+ (run-hooks 'cider-repl-history-hook)))
+ (setq buffer-read-only t))))
+
+(defun cider-repl-history-update ()
+ "Update the history buffer to reflect the latest state of the command history."
+ (interactive)
+ (cl-assert (eq major-mode 'cider-repl-history-mode))
+ (cider-repl-history-setup cider-repl-history-repl-window
+ cider-repl-history-repl-buffer
+ (current-buffer))
+ (cider-repl-history-resize-window))
+
+(defun cider-repl-history-occur (regexp)
+ "Display all command history entries matching REGEXP."
+ (interactive
+ (list (cider-repl-history-read-regexp
+ "Display command history entries matching" nil)))
+ (cl-assert (eq major-mode 'cider-repl-history-mode))
+ (cider-repl-history-setup cider-repl-history-repl-window
+ cider-repl-history-repl-buffer
+ (current-buffer)
+ regexp)
+ (cider-repl-history-resize-window))
+
+(put 'cider-repl-history-mode 'mode-class 'special)
+(define-derived-mode cider-repl-history-mode clojure-mode "History"
+ "Major mode for browsing the entries in the command input history.
+
+\\{cider-repl-history-mode-map}"
+ (define-key cider-repl-history-mode-map (kbd "n") 'cider-repl-history-forward)
+ (define-key cider-repl-history-mode-map (kbd "p") 'cider-repl-history-previous)
+ (define-key cider-repl-history-mode-map (kbd "SPC") 'cider-repl-history-insert-and-quit)
+ (define-key cider-repl-history-mode-map (kbd "RET") 'cider-repl-history-insert-and-quit)
+ (define-key cider-repl-history-mode-map [(mouse-2)] 'cider-repl-history-mouse-insert)
+ (define-key cider-repl-history-mode-map (kbd "l") 'cider-repl-history-occur)
+ (define-key cider-repl-history-mode-map (kbd "s") 'cider-repl-history-search-forward)
+ (define-key cider-repl-history-mode-map (kbd "r") 'cider-repl-history-search-backward)
+ (define-key cider-repl-history-mode-map (kbd "g") 'cider-repl-history-update)
+ (define-key cider-repl-history-mode-map (kbd "q") 'cider-repl-history-quit)
+ (define-key cider-repl-history-mode-map (kbd "U") 'cider-repl-history-undo-other-window)
+ (define-key cider-repl-history-mode-map (kbd "?") 'describe-mode)
+ (define-key cider-repl-history-mode-map (kbd "h") 'describe-mode))
+
+;;;###autoload
+(defun cider-repl-history ()
+ "Display items in the CIDER command history in another buffer."
+ (interactive)
+ (when (eq major-mode 'cider-repl-history-mode)
+ (user-error "Already viewing the CIDER command history"))
+
+ (let* ((repl-win (selected-window))
+ (repl-buf (window-buffer repl-win))
+ (buf (get-buffer-create cider-repl-history-buffer)))
+ (cider-repl-history-setup repl-win repl-buf buf)
+ (pop-to-buffer buf)
+ (cider-repl-history-resize-window)))
+
+(provide 'cider-repl-history)
+
+;;; cider-repl-history.el ends here
diff --git a/cider-repl.el b/cider-repl.el
index 807958aa9..23c778a88 100644
--- a/cider-repl.el
+++ b/cider-repl.el
@@ -1168,6 +1168,7 @@ constructs."
(declare-function cider-undef "cider-interaction")
(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-interaction")
(declare-function cider-refresh "cider-interaction")
(cider-repl-add-shortcut "clear-output" #'cider-repl-clear-output)
@@ -1178,6 +1179,7 @@ constructs."
(cider-repl-add-shortcut "toggle-pretty" #'cider-repl-toggle-pretty-printing)
(cider-repl-add-shortcut "browse-ns" (lambda () (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-refresh)
@@ -1285,6 +1287,7 @@ constructs."
(define-key map (kbd "C-c M-d") #'cider-display-connection-info)
(define-key map (kbd "C-c C-q") #'cider-quit)
(define-key map (kbd "C-c M-i") #'cider-inspect)
+ (define-key map (kbd "C-c M-p") #'cider-repl-history)
(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-refresh)
diff --git a/cider-util.el b/cider-util.el
index 0fec7e377..2c3eafb05 100644
--- a/cider-util.el
+++ b/cider-util.el
@@ -610,6 +610,7 @@ through a stack of help buffers. Variables `help-back-label' and
"Press <\\[cider-drink-a-sip]> to get more CIDER tips."
"Press <\\[cider-browse-ns-all]> to start CIDER's namespace browser."
"Press <\\[cider-classpath]> to start CIDER's classpath browser."
+ "Press <\\[cider-repl-history]> to start CIDER's REPL input history browser."
"Press <\\[cider-macroexpand-1]> to expand the preceding macro."
"Press <\\[cider-inspect]> to inspect the preceding expression's result."
"Press to inspect the defun at point's result."
diff --git a/cider.el b/cider.el
index 2241a013b..fc6450171 100644
--- a/cider.el
+++ b/cider.el
@@ -86,6 +86,7 @@ project inference will take place."
(require 'cider-compat)
(require 'cider-debug)
(require 'tramp-sh)
+(require 'cider-repl-history)
(require 'seq)
diff --git a/doc/images/history_browser.png b/doc/images/history_browser.png
new file mode 100644
index 000000000..9b9b43c8c
Binary files /dev/null and b/doc/images/history_browser.png differ
diff --git a/doc/index.md b/doc/index.md
index 91c226af1..7c5022fb8 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -74,6 +74,7 @@ CIDER packs plenty of features. Here are some of them (in no particular order):
* [Pretty-printing of results](configuration.md#pretty-printing)
* [Classpath browser](miscellaneous_features.md#classpath-browser)
* [Namespace browser](miscellaneous_features.md#namespace-browser)
+* [REPL history browser](miscellaneous_features.md#repl-history-browser)
* nREPL session management
* [Scratchpad](miscellaneous_features.md#using-a-scratchpad)
* [Minibuffer code evaluation](miscellaneous_features.md#evaluating-clojure-code-in-the-minibuffer)
diff --git a/doc/miscellaneous_features.md b/doc/miscellaneous_features.md
index bdc3283a0..cc74e793f 100644
--- a/doc/miscellaneous_features.md
+++ b/doc/miscellaneous_features.md
@@ -190,6 +190,147 @@ Keyboard shortcut | Description
n | Go to next line.
p | Go to previous line.
+## REPL history browser
+
+You can browse your REPL input history with the command M-x
+`cider-repl-history`. It is also bound in `cider-repl-mode` buffers to
+C-c M-p, and is also available via the `history` shortcut.
+
+The history is displayed in order, with the most recent input at the top of the
+buffer, and the oldest one at the bottom. You can scroll through the history,
+and when you find the history item you were looking for, you can insert it from
+the history buffer into your REPL buffer.
+
+
+
+### Mode
+
+The history buffer has its own major mode, `cider-repl-history-mode` which is derived
+from `clojure-mode`, so you get fontification in the history buffer. It supports
+the expected defcustom hook variable, `cider-repl-history-hook`.
+
+### Insertion
+
+Typically your cursor will be at the bottom of the REPL buffer (`point-max`)
+when you use this feature; if that's the case, the text is inserted, and point
+is advanced to the end of the inserted text. In the unusual case where you
+invoke the history browser when your cursor is _not_ at the end of the buffer,
+the text is _still_ inserted at point-max, but point is not modified.
+
+The text is inserted without a final newline, meaning you can edit the form
+if you wish, and you must explicitly hit Enter to have it evaluated
+by the REPL.
+
+### Quitting
+
+After text is inserted, the history buffer is automatically quit. If you decide
+you don't want to insert any text after all, you can explicitly quit by running
+`cider-repl-history-quit` (see keyboard shortcuts). Due to the initialization and
+cleanup done, it is better to properly quit, rather than just switch away from
+the history buffer.
+
+When you quit the history buffer, there are several different ways for the
+buffers and windows to be restored. This is controlled by the custom variable
+`cider-repl-history-quit-action`, which can be assigned one of several values:
+
+- `quit-window` restores the window configuration to what it was before.
+ This is the default.
+- `delete-and-restore` restores the window configuration to what it was before,
+ and kills the `*cider-repl-history*` buffer.
+- `kill-and-delete-window` kills the `*cider-repl-history*` buffer, and
+ deletes the window.
+- `bury-buffer` simply buries the `*cider-repl-history*` buffer, but keeps the
+ window.
+- `bury-and-delete-window` buries the buffer, and (if there is more than one
+ window) deletes the window.
+- any other value is interpreted as the name of a function to call
+
+### Filtering
+
+By invoking `cider-repl-history-occur` from the history buffer, you will be prompted
+for a regular expression, and the history buffer will be filtered to only those
+inputs that match the regexp.
+
+### Preview and Highlight
+
+When `cider-repl-history-show-preview` is non-nil, we display an [`overlay`]
+(https://www.gnu.org/software/emacs/manual/html_node/elisp/Overlays.html)
+of the currently selected history entry, in the REPL buffer.
+
+This is a nice feature; the only thing to be careful of is that if you do not
+properly quit from browsing the history (i.e., if you just C-x b
+away from the buffer), you may be left with an unwanted overlay in your REPL
+buffer. It can be eliminated with M-x `cider-repl-history-clear-preview`.
+
+By default, the variable is nil and the feature is off.
+
+A related feature is to highlight the entry once it is actually inserted into
+the REPL buffer. This is controlled by the variable
+`cider-repl-history-highlight-inserted-item`. The non-nil value selected controls how
+the inserted item is highlighted, possible values are `solid` (highlight the
+inserted text for a fixed period of time), or `pulse` (fade out the highlighting
+gradually). Setting this variable to the value t will select the default
+highlighting style, which currently `pulse`. Default is nil.
+
+When "highlight-inserted" is turned on, you can customize the face of the
+inserted text with the variable `cider-repl-history-inserted-item-face`.
+
+### Additional Customization
+
+There are quite a few customizations available, in addition to the ones
+already mentioned.
+
+- `cider-repl-history-display-duplicates` - when set to `nil`, will not display any
+ duplicate entries in the history buffer. Default is `t`.
+- `cider-repl-history-display-duplicate-highest` - when not displaying duplicates,
+ this controls where in the history the one instance of the duplicated text
+ is displayed. When `t`, it displays the entry in the highest position
+ applicable; when `nil`, it displays it in the lowest position.
+- `cider-repl-history-display-style` - the history entries will often be more than
+ one line. The package gives you two options for displaying the entries:
+ - `separated` - a separator string is inserted between entries; entries
+ may span multiple lines. This is the default.
+ - `one-line` - any newlines are replaced with literal `\n` strings, and
+ therefore no separator is necessary. Each `\n` becomes a proper newline
+ when the text is inserted into the REPL.
+- `cider-repl-history-separator` - when `cider-repl-history-display-style` is `separated`,
+ this gives the text to use as the separator. The default is a series of ten
+ semicolons, which is, of course, a comment in Clojure. The separator could be
+ anything, but it may screw up the fontification if you make it something weird.
+- `cider-repl-history-separator-face` - specifies the face for the separator.
+- `cider-repl-history-maximum-display-length` - when nil (the default), all history
+ items are displayed in full. If you prefer to have long items abbreviated,
+ you can set this variable to an integer, and each item will be limited to that
+ many characters. (This variable does not affect the number of items displayed,
+ only the maximum length of each item.)
+- `cider-repl-history-recenter` - when non-nil, always keep the current entry at the
+ top of the history window. Default is nil.
+- `cider-repl-history-resize-window` - whether to resize the history window to fit
+ its contents. Value is either t, meaning yes, or a cons pair of integers,
+ (MAXIMUM . MINIMUM) for the size of the window. MAXIMUM defaults to the window
+ size chosen by `pop-to-buffer`; MINIMUM defaults to `window-min-height`.
+- `cider-repl-history-highlight-current-entry` - if non-nil, highlight the currently
+ selected entry in the history buffer. Default is nil.
+- `cider-repl-history-current-entry-face` - specifies the face for the history-entry
+ highlight.
+- `cider-repl-history-text-properties` - when set to `t`, maintains Emacs text
+ properties on the entry. Default is `nil`.
+
+### Key Bindings
+
+There are a number of important keybindings in history buffers.
+
+Keyboard shortcut | Description
+---------------------------------|-------------------------------
+n | Go to next (lower, older) item in the history.
+p | Go to previous (higher, more recent) item in the history.
+RET or SPC | Insert history item (at point) at the end of the REPL buffer, and quit.
+l (lower-case L) | Filter the command history (see **Filtering**, above).
+s | Regexp search forward.
+r | Regexp search backward.
+q | Quit (and take quit action).
+U | Undo in the REPL buffer.
+
## Documentation buffers include "See Also" references
You can add references to other vars by including their names in `` ` `` in the docstring.
diff --git a/doc/using_the_repl.md b/doc/using_the_repl.md
index 2118ea056..8d11d3aae 100644
--- a/doc/using_the_repl.md
+++ b/doc/using_the_repl.md
@@ -233,3 +233,6 @@ section of your Leiningen project's configuration.
Note that the history is written to the file when you kill the REPL
buffer (which includes invoking `cider-quit`) or you quit Emacs.
+
+There is a facility to browse the REPL history; see `REPL input history browser`
+in [Miscellaneous Features](miscellaneous_features.md#repl-history-browser)