Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a command to delete history item at point #3574

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## master (unreleased)

### New features

- CIDER [History](https://docs.cider.mx/cider/repl/history.html): if `cider-repl-history-file` is unset, the history is saved on a per-project basis.
- CIDER [History](https://docs.cider.mx/cider/repl/history.html): Add a command to delete history item at point.

### Changes

- Bump the injected `enrich-classpath` to [1.18.4](https://github.com/clojure-emacs/enrich-classpath/compare/v1.18.2...v1.18.4).
Expand Down
31 changes: 22 additions & 9 deletions cider-repl-history.el
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,14 @@ call `cider-repl-history' again.")
(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)
(if cider-repl-history-file
cider-repl-input-global-history
(buffer-local-value
'cider-repl-input-local-history
cider-repl-history-repl-buffer))
(error "Variable `cider-repl-history-repl-buffer' not bound to a buffer")))

(defun cider-repl-history-resize-window ()
Expand Down Expand Up @@ -576,6 +577,16 @@ text from the *cider-repl-history* buffer."
(with-current-buffer cider-repl-history-repl-buffer
(undo)))

(defun cider-repl-history-delete ()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete-item-at-point

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or entry)

"Delete history item (at point)."
(interactive)
(let* ((orig (point))
(str (cider-repl-history-current-string orig)))
(with-current-buffer cider-repl-history-repl-buffer
(delete str (cider-repl-get-history)))
(cider-repl-history-update)
(goto-char orig)))

(defun cider-repl-history-setup (repl-win repl-buf history-buf &optional regexp)
"Setup.
REPL-WIN and REPL-BUF are where to insert commands;
Expand Down Expand Up @@ -637,16 +648,17 @@ HISTORY-BUF is the history, and optional arg REGEXP is a filter."
#'cider-repl-history-update-highlighted-entry
nil t))
(message
(let ((entry (if (= 1 (length cider-command-history))
"entry"
"entries")))
(let* ((history-length (length cider-command-history))
(entry (if (= 1 history-length)
"entry"
"entries")))
(concat
(if (and (not regexp)
cider-repl-history-display-duplicates)
(format "%s %s in the command history."
(length cider-command-history) entry)
history-length entry)
(format "%s (of %s) %s in the command history shown."
(length items) (length cider-command-history) entry))
(length items) history-length entry))
(substitute-command-keys
(concat " Type \\[cider-repl-history-quit] to quit. "
"\\[describe-mode] for help.")))))
Expand Down Expand Up @@ -693,6 +705,7 @@ HISTORY-BUF is the history, and optional arg REGEXP is a filter."
(define-key map (kbd "g") #'cider-repl-history-update)
(define-key map (kbd "q") #'cider-repl-history-quit)
(define-key map (kbd "U") #'cider-repl-history-undo-other-window)
(define-key map (kbd "D") #'cider-repl-history-delete)
(define-key map (kbd "?") #'describe-mode)
(define-key map (kbd "h") #'describe-mode)
map))
Expand Down
120 changes: 79 additions & 41 deletions cider-repl.el
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,9 @@ CIDER 1.7."
This property value must be unique to avoid having adjacent inputs be
joined together.")

(defvar-local cider-repl-input-history '()
(defvar-local cider-repl-input-local-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'.")
Expand All @@ -208,6 +205,10 @@ Currently its only purpose is to facilitate `cider-repl-clear-buffer'.")
"Marker for the end of output.
Currently its only purpose is to facilitate `cider-repl-clear-buffer'.")

(defvar-local cider-repl--history-local-or-global-file nil
"File to save the persistent REPL history to.
Depending on if `cider-repl-history-file' is set or not")

(defun cider-repl-tab ()
"Invoked on TAB keystrokes in `cider-repl-mode' buffers."
(interactive)
Expand Down Expand Up @@ -1472,9 +1473,10 @@ WIN, BUFFER and POS are the window, buffer and point under mouse position."
"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)))
(equal string (car (cider-repl-get-history))))
(if cider-repl-history-file
(push string cider-repl-input-global-history)
(push string cider-repl-input-local-history))))

(defun cider-repl-delete-current-input ()
"Delete all text after the prompt."
Expand All @@ -1494,7 +1496,7 @@ Return -1 resp the length of the history if no item matches."
(let* ((step (cl-ecase direction
(forward -1)
(backward 1)))
(history cider-repl-input-history)
(history (cider-repl-get-history))
(len (length history)))
(cl-loop for pos = (+ start-pos step) then (+ pos step)
if (< pos 0) return -1
Expand All @@ -1507,14 +1509,14 @@ 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))
(max-pos (length (cider-repl-get-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))
(cider-repl--replace-input (nth pos (cider-repl-get-history)))
(setq msg (format "History item: %d" pos)))
((not cider-repl-wrap-history)
(setq msg (cond ((= pos min-pos) "End of history")
Expand Down Expand Up @@ -1593,10 +1595,21 @@ If USE-CURRENT-INPUT is non-nil, use the current input."
:safe #'integerp)

(defcustom cider-repl-history-file nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't just delete this - you'll have to add a deprecation warning for it, so people would know it's no longer being used. If we want to avoid breaking changes we'd probably want to keep the old behavior if this variable is set.

"File to save the persistent REPL history to."
"File to save the persistent REPL history to.
If this is set, the history will be global to all projects. Otherwise, the
history is local per project and stored in a file (named by
`cider-repl-local-history-name') at its root."

:type 'string
:safe #'stringp)

(defconst cider-repl-local-history-name ".cider-history"
"Name of the local history file (if `cider-repl-history-file' is not set).
It will suffixed by the REPL type.")

(defvar cider-repl-input-global-history '()
"History list of strings read from all REPL buffers.")

(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: "
Expand All @@ -1612,28 +1625,62 @@ It does not yet set the input history."
(read (current-buffer))))
'()))

(defun cider-repl-get-history ()
"Function to retrieve history from the REPL buffer."
(if cider-repl-history-file
cider-repl-input-global-history
cider-repl-input-local-history))

(defun cider-repl--find-dir-for-history ()
"Find the first suitable directory to store the local history."
(seq-find
(lambda (dir) (and (not (null dir)) (not (tramp-tramp-file-p dir))))
(list nrepl-project-dir (clojure-project-dir) default-directory)))

(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.
defined filenames can be used to read special history files. If
`cider-repl-history-file' is not set either then the local history file of
the current project (see `cider-repl-history-file' for more info) is loaded.

The value of `cider-repl-input-history' is set by this function."
The value of `cider-repl-get-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))))
(setq cider-repl--history-local-or-global-file
(or filename cider-repl-history-file))
(when (not cider-repl--history-local-or-global-file)
(setq
cider-repl--history-local-or-global-file
(when-let* ((dir (cider-repl--find-dir-for-history)))
(expand-file-name
(concat cider-repl-local-history-name "-" (symbol-name (cider-runtime)))
dir))))
(when cider-repl--history-local-or-global-file
(condition-case nil
(progn
;; 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.
(let ((input-history (cider-repl--history-read
cider-repl--history-local-or-global-file)))
(if cider-repl-history-file
(setq cider-repl-input-global-history input-history)
(setq cider-repl-input-local-history input-history)))
(add-hook 'kill-buffer-hook #'cider-repl-history-just-save t t)
(add-hook 'kill-emacs-hook #'cider-repl-history-save-all))
(error
(message
"Malformed history file: %s"
cider-repl--history-local-or-global-file)))))

(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)))
(let* ((end (min (length (cider-repl-get-history)) cider-repl-history-size))
;; newest items are at the beginning of the list, thus 0
(hist (cl-subseq mhist 0 (min (length mhist) cider-repl-history-size))))
(hist (cl-subseq (cider-repl-get-history) 0 end)))
(unless (file-writable-p filename)
(error (format "History file not writable: %s" filename)))
(let ((print-length nil) (print-level nil))
Expand All @@ -1649,24 +1696,21 @@ utf-8-unix."
"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)))
(let* ((file (or filename cider-repl--history-local-or-global-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))
(cider-repl-history-save cider-repl--history-local-or-global-file))

(defun cider-repl-history-save-all ()
"Save all histories."
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (equal major-mode 'cider-repl-mode)
(cider-repl-history-just-save)))))


;;; REPL shortcuts
Expand Down Expand Up @@ -2053,13 +2097,7 @@ in an unexpected place."
(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
(condition-case nil
(cider-repl-history-load cider-repl-history-file)
(error
(message "Malformed cider-repl-history-file: %s" 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))
(cider-repl-history-load)
(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)))
(cider-repl-setup-paredit))
Expand Down
9 changes: 6 additions & 3 deletions doc/modules/ROOT/pages/repl/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,15 @@ reset automatically by the `track-state` middleware.
(setq cider-repl-history-size 1000) ; the default is 500
----

* To store the REPL history in a file:
* To share all the REPL histories in a single file:

[source,lisp]
----
(setq cider-repl-history-file "path/to/file")
----

Note that CIDER writes the history to the file when you kill the REPL
buffer, which includes invoking `cider-quit`, or when you quit Emacs.
Note that CIDER stores the history in a file named by
`cider-repl-local-history-name` located at the root of your project if
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably this should be named cider-repl-project-history-file or something along those lines, as I think local history might be slightly misleading in this case. (and yeah - I realize there might not always be a project associated with a REPL)

the `cider-repl-history-file` is not set. It writes to it when you
kill the REPL buffer, which includes invoking `cider-quit`, or when
you quit Emacs.
3 changes: 3 additions & 0 deletions doc/modules/ROOT/pages/repl/history.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,7 @@ There are a number of important keybindings in history buffers.

| kbd:[U]
| Undo in the REPL buffer.

| kbd:[D]
| Delete history item (at point).
|===