From c9c2a6b32e8766ffa344e945867a8412f61bdce9 Mon Sep 17 00:00:00 2001 From: Zzull Date: Thu, 9 Nov 2023 17:18:33 +0100 Subject: [PATCH 1/7] Store history on a per-project basis by default --- CHANGELOG.md | 4 ++ cider-repl.el | 60 ++++++++++++++----- .../ROOT/pages/repl/configuration.adoc | 8 ++- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b10aa867f..f2f6fd330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 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. + ### Changes - Bump the injected `enrich-classpath` to [1.18.4](https://github.com/clojure-emacs/enrich-classpath/compare/v1.18.2...v1.18.4). diff --git a/cider-repl.el b/cider-repl.el index 173b0dc54..40476cfd2 100644 --- a/cider-repl.el +++ b/cider-repl.el @@ -208,6 +208,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) @@ -1593,7 +1597,10 @@ If USE-CURRENT-INPUT is non-nil, use the current input." :safe #'integerp) (defcustom cider-repl-history-file nil - "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 (.cider-history) at its +root." :type 'string :safe #'stringp) @@ -1615,15 +1622,35 @@ It does not yet set the input history." (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." (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 (clojure-project-dir))) + (expand-file-name ".cider-history" 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. + (setq + cider-repl-input-history + (cider-repl--history-read cider-repl--history-local-or-global-file)) + (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. @@ -1649,14 +1676,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)) + (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))))) ;; SLIME has different semantics and will not save any duplicates. ;; we keep track of how many items were added to the history in the @@ -2053,13 +2087,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)) diff --git a/doc/modules/ROOT/pages/repl/configuration.adoc b/doc/modules/ROOT/pages/repl/configuration.adoc index 774abc889..c86542f06 100644 --- a/doc/modules/ROOT/pages/repl/configuration.adoc +++ b/doc/modules/ROOT/pages/repl/configuration.adoc @@ -340,12 +340,14 @@ 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 called `.cider-history` +located at the root of your project if 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. From b7a5c4d190109a492a17c7e5493001b5c554e647 Mon Sep 17 00:00:00 2001 From: Zzull Date: Thu, 9 Nov 2023 18:19:54 +0100 Subject: [PATCH 2/7] Really share the histories when `cider-repl-history-file' is set --- cider-repl-history.el | 9 +++++---- cider-repl.el | 35 ++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/cider-repl-history.el b/cider-repl-history.el index e5044bf6e..3bafe13d9 100644 --- a/cider-repl-history.el +++ b/cider-repl-history.el @@ -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 () diff --git a/cider-repl.el b/cider-repl.el index 40476cfd2..6bb69e95d 100644 --- a/cider-repl.el +++ b/cider-repl.el @@ -194,7 +194,7 @@ 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 @@ -1476,8 +1476,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) + (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)) (cl-incf cider-repl-input-history-items-added))) (defun cider-repl-delete-current-input () @@ -1498,7 +1500,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 @@ -1511,14 +1513,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") @@ -1604,6 +1606,9 @@ root." :type 'string :safe #'stringp) +(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: " @@ -1619,6 +1624,12 @@ 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-history-load (&optional filename) "Load history from FILENAME into current session. FILENAME defaults to the value of `cider-repl-history-file' but user @@ -1626,7 +1637,7 @@ 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))) (setq cider-repl--history-local-or-global-file (or filename cider-repl-history-file)) @@ -1642,9 +1653,11 @@ The value of `cider-repl-input-history' is set by this function." ;; 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 cider-repl--history-local-or-global-file)) + (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 @@ -1656,7 +1669,7 @@ The value of `cider-repl-input-history' is set by this function." "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 + (let* ((mhist (cider-repl--histories-merge (cider-repl-get-history) cider-repl-input-history-items-added (cider-repl--history-read filename))) ;; newest items are at the beginning of the list, thus 0 From 132bcc40bdd67d75270c4b472eafe4d408e81c12 Mon Sep 17 00:00:00 2001 From: Zzull Date: Thu, 9 Nov 2023 19:00:33 +0100 Subject: [PATCH 3/7] Set the name of the local history file according to the REPL type --- cider-repl.el | 13 ++++++++++--- doc/modules/ROOT/pages/repl/configuration.adoc | 9 +++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cider-repl.el b/cider-repl.el index 6bb69e95d..16d266c46 100644 --- a/cider-repl.el +++ b/cider-repl.el @@ -1601,11 +1601,16 @@ If USE-CURRENT-INPUT is non-nil, use the current input." (defcustom cider-repl-history-file nil "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 (.cider-history) at its -root." +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.") @@ -1645,7 +1650,9 @@ The value of `cider-repl-get-history' is set by this function." (setq cider-repl--history-local-or-global-file (when-let* ((dir (clojure-project-dir))) - (expand-file-name ".cider-history" dir)))) + (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 diff --git a/doc/modules/ROOT/pages/repl/configuration.adoc b/doc/modules/ROOT/pages/repl/configuration.adoc index c86542f06..17428d63a 100644 --- a/doc/modules/ROOT/pages/repl/configuration.adoc +++ b/doc/modules/ROOT/pages/repl/configuration.adoc @@ -347,7 +347,8 @@ reset automatically by the `track-state` middleware. (setq cider-repl-history-file "path/to/file") ---- -Note that CIDER stores the history in a file called `.cider-history` -located at the root of your project if 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. +Note that CIDER stores the history in a file named by +`cider-repl-local-history-name` located at the root of your project if +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. From 2bbe7841f3a266e0c417cf4d018532afb0791357 Mon Sep 17 00:00:00 2001 From: Zzull Date: Wed, 8 Nov 2023 00:53:41 +0100 Subject: [PATCH 4/7] Stop merging the input history with `cider-repl-history-file' Because input history cannot be changed by another REPL anymore --- cider-repl.el | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/cider-repl.el b/cider-repl.el index 16d266c46..164d54cab 100644 --- a/cider-repl.el +++ b/cider-repl.el @@ -197,9 +197,6 @@ joined together.") (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'.") @@ -1479,8 +1476,7 @@ Empty strings and duplicates are ignored." (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)) - (cl-incf cider-repl-input-history-items-added))) + (push string cider-repl-input-local-history)))) (defun cider-repl-delete-current-input () "Delete all text after the prompt." @@ -1676,11 +1672,9 @@ The value of `cider-repl-get-history' is set by this function." "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-get-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)) @@ -1712,16 +1706,6 @@ constructs." (when (equal major-mode 'cider-repl-mode) (cider-repl-history-just-save))))) -;; 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 ?\, From 3d001d7b4ef4858081092e5d1274022b4d1c891e Mon Sep 17 00:00:00 2001 From: Zzull Date: Fri, 10 Nov 2023 13:11:22 +0100 Subject: [PATCH 5/7] Find a suitable directory to store the local history to more robustly --- cider-repl.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cider-repl.el b/cider-repl.el index 164d54cab..d7c38eeea 100644 --- a/cider-repl.el +++ b/cider-repl.el @@ -1631,6 +1631,12 @@ It does not yet set the input history." 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 @@ -1645,7 +1651,7 @@ The value of `cider-repl-get-history' is set by this function." (when (not cider-repl--history-local-or-global-file) (setq cider-repl--history-local-or-global-file - (when-let* ((dir (clojure-project-dir))) + (when-let* ((dir (cider-repl--find-dir-for-history))) (expand-file-name (concat cider-repl-local-history-name "-" (symbol-name (cider-runtime))) dir)))) From 959e5d332851ec05e7d2748f824b19f3e528d2ba Mon Sep 17 00:00:00 2001 From: Zzull Date: Tue, 7 Nov 2023 23:38:16 +0100 Subject: [PATCH 6/7] Calculate the length of the history only once --- cider-repl-history.el | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cider-repl-history.el b/cider-repl-history.el index 3bafe13d9..db167db7a 100644 --- a/cider-repl-history.el +++ b/cider-repl-history.el @@ -638,16 +638,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."))))) From 41cccdfd9afe055b2567dc855e4cfd79f32a19dc Mon Sep 17 00:00:00 2001 From: Zzull Date: Wed, 8 Nov 2023 00:53:23 +0100 Subject: [PATCH 7/7] Add a command to delete history item at point --- CHANGELOG.md | 1 + cider-repl-history.el | 11 +++++++++++ doc/modules/ROOT/pages/repl/history.adoc | 3 +++ 3 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f6fd330..36e8d8768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### 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 diff --git a/cider-repl-history.el b/cider-repl-history.el index db167db7a..f397ae76b 100644 --- a/cider-repl-history.el +++ b/cider-repl-history.el @@ -577,6 +577,16 @@ text from the *cider-repl-history* buffer." (with-current-buffer cider-repl-history-repl-buffer (undo))) +(defun cider-repl-history-delete () + "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; @@ -695,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)) diff --git a/doc/modules/ROOT/pages/repl/history.adoc b/doc/modules/ROOT/pages/repl/history.adoc index 7e095ee43..88a34a1a2 100644 --- a/doc/modules/ROOT/pages/repl/history.adoc +++ b/doc/modules/ROOT/pages/repl/history.adoc @@ -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). |===