aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPengji Zhang2024-12-10 18:55:36 +0800
committerJim Porter2024-12-11 11:56:45 -0800
commit3959ea66448fb371cdc67bd963cd539a90f99ee5 (patch)
tree8f4094a990a4eda26381e5cc7ece15365d17db04
parent6a07dc19ac65eaa3ee1eeb814af85bde7fd2c509 (diff)
downloademacs-3959ea66448fb371cdc67bd963cd539a90f99ee5.tar.gz
emacs-3959ea66448fb371cdc67bd963cd539a90f99ee5.zip
Rework history Isearch for Eshell
This is to make history Isearch for Eshell similar to that of 'comint-mode', by hooking into Isearch properly instead of defining new commands to emulate Isearch (bug#74287). * lisp/eshell/em-hist.el (eshell-history-isearch): New user option. (eshell-goto-history, eshell--isearch-setup) (eshell-history-isearch-end, eshell-history-isearch-search) (eshell-history-isearch-message, eshell-history-isearch-wrap) (eshell-history-isearch-push-state): New functions. (eshell-isearch-backward-regexp, eshell-isearch-forward-regexp): New commands. (eshell--history-isearch-message-overlay) (eshell--stored-incomplete-input, eshell--force-history-isearch): New internal variables. (eshell-hist-mode-map): Bind 'M-r' to 'eshell-isearch-backward-regexp' and free 'M-s' binding for normal in-buffer search commands. (eshell-isearch-backward, eshell-isearch-forward): Use the new way to start searching. (eshell-hist-initialize): Use the new Isearch setup function. (eshell-previous-matching-input): Use 'eshell-goto-history'. Also inhibit messages when searching. (eshell-isearch-map, eshell-isearch-repeat-backward) (eshell-isearch-abort, eshell-isearch-delete-char) (eshell-isearch-return, eshell-isearch-cancel) (eshell-isearch-repeat-forward, eshell-test-imatch) (eshell-return-to-prompt, eshell-prepare-for-search): Remove. These are for the old history Isearch implementation. * doc/misc/eshell.texi (History): Document changes. * etc/NEWS: Annouce changes.
-rw-r--r--doc/misc/eshell.texi15
-rw-r--r--etc/NEWS23
-rw-r--r--lisp/eshell/em-hist.el326
3 files changed, 227 insertions, 137 deletions
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 709417a7efb..264bb655e5e 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -2660,10 +2660,10 @@ navigation and searching are bound to different keys:
2660 2660
2661@table @kbd 2661@table @kbd
2662@kindex M-r 2662@kindex M-r
2663@kindex M-s
2664@item M-r 2663@item M-r
2665@itemx M-s 2664History I-search. @kbd{M-r} starts an incremental search in input
2666History I-search. 2665history. While searching, type @kbd{C-r} to move to the previous match,
2666and @kbd{C-s} to move to the next match in the input history.
2667 2667
2668@kindex M-p 2668@kindex M-p
2669@kindex M-n 2669@kindex M-n
@@ -2674,6 +2674,15 @@ line when you run these commands, they will instead jump to the
2674previous or next line that begins with that string. 2674previous or next line that begins with that string.
2675@end table 2675@end table
2676 2676
2677@vindex eshell-history-isearch
2678If you would like to use the default Isearch key-bindings to search
2679through input history, you may customize @code{eshell-history-isearch}
2680to @code{t}. That makes, for example, @kbd{C-r} and @kbd{C-M-r} in an
2681Eshell buffer search in input history only. In addition, if the value
2682of @code{eshell-history-isearch} is @code{dwim}, those commands search
2683in the history when the point is after the last prompt, and search in
2684the buffer when the point is before or within the last prompt.
2685
2677@node Extension modules 2686@node Extension modules
2678@chapter Extension modules 2687@chapter Extension modules
2679Eshell provides a facility for defining extension modules so that they 2688Eshell provides a facility for defining extension modules so that they
diff --git a/etc/NEWS b/etc/NEWS
index 3efce149dbf..e6227009725 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -380,6 +380,29 @@ This hook runs after an Eshell session has been fully initialized,
380immediately before running 'eshell-post-command-hook' for the first 380immediately before running 'eshell-post-command-hook' for the first
381time. 381time.
382 382
383+++
384*** Improved history Isearch.
385History Isearch in Eshell is reworked. Two new commands
386'eshell-isearch-backward-regexp' and 'eshell-isearch-forward-regexp' are
387added for incrementally searching through the input history.
388'eshell-isearch-backward-regexp' is bound to 'M-r' by default, and 'M-s'
389is freed for normal search commands. If you would like to restore the
390previous key-bindings for the non-incremental search commands, put in
391your configuration:
392
393 (keymap-set eshell-hist-mode-map "M-r"
394 #'eshell-previous-matching-input)
395 (keymap-set eshell-hist-mode-map "M-s"
396 #'eshell-next-matching-input)
397
398+++
399*** New user option 'eshell-history-isearch'
400When 'eshell-history-isearch' is nil (the default), Isearch commands
401search in the buffer contents. If you customize it to t, those commands
402only search in input history. If you customize it to the symbol 'dwim',
403those commands search in input history only when the point is after the
404last prompt.
405
383** SHR 406** SHR
384 407
385+++ 408+++
diff --git a/lisp/eshell/em-hist.el b/lisp/eshell/em-hist.el
index fffd611c06f..4bcf434f6e4 100644
--- a/lisp/eshell/em-hist.el
+++ b/lisp/eshell/em-hist.el
@@ -34,7 +34,6 @@
34;; Also, most of `comint-mode's keybindings are accepted: 34;; Also, most of `comint-mode's keybindings are accepted:
35;; 35;;
36;; M-r ; search backward for a previous command by regexp 36;; M-r ; search backward for a previous command by regexp
37;; M-s ; search forward for a previous command by regexp
38;; M-p ; access the last command entered, repeatable 37;; M-p ; access the last command entered, repeatable
39;; M-n ; access the first command entered, repeatable 38;; M-n ; access the first command entered, repeatable
40;; 39;;
@@ -132,6 +131,17 @@ whitespace."
132 (function :tag "Other function")) 131 (function :tag "Other function"))
133 :risky t) 132 :risky t)
134 133
134(defcustom eshell-history-isearch nil
135 "Non-nil to Isearch in input history only.
136If t, usual Isearch keys like \\[isearch-forward] in Eshell search in
137the input history only. If `dwim', Isearch in the input history when
138point is at the command line, otherwise search in the current Eshell
139buffer."
140 :type '(choice (const :tag "Don't search in input history" nil)
141 (const :tag "Search histroy when point is on command line" dwim)
142 (const :tag "Always search in input history" t))
143 :version "31.1")
144
135(defun eshell-hist--update-keymap (symbol value) 145(defun eshell-hist--update-keymap (symbol value)
136 "Update `eshell-hist-mode-map' for `eshell-hist-match-partial'." 146 "Update `eshell-hist-mode-map' for `eshell-hist-match-partial'."
137 ;; Don't try to set this before it is bound. See below. 147 ;; Don't try to set this before it is bound. See below.
@@ -204,25 +214,20 @@ element, regardless of any text on the command line. In that case,
204(defvar eshell-hist--new-items nil 214(defvar eshell-hist--new-items nil
205 "The number of new history items that have not been written to 215 "The number of new history items that have not been written to
206file. This variable is local in each eshell buffer.") 216file. This variable is local in each eshell buffer.")
207 217(defvar-local eshell--history-isearch-message-overlay nil
208(defvar-keymap eshell-isearch-map 218 "Overlay for Isearch message when searching through input history.")
209 :doc "Keymap used in isearch in Eshell." 219(defvar-local eshell--stored-incomplete-input nil
210 :parent isearch-mode-map 220 "Stored input for history cycling.")
211 "C-m" #'eshell-isearch-return 221(defvar eshell--force-history-isearch nil
212 "C-r" #'eshell-isearch-repeat-backward 222 "Non-nil means to force searching in input history.
213 "C-s" #'eshell-isearch-repeat-forward 223If nil, respect the option `eshell-history-isearch'.")
214 "C-g" #'eshell-isearch-abort
215 "<backspace>" #'eshell-isearch-delete-char
216 "<delete>" #'eshell-isearch-delete-char
217 "C-c C-c" #'eshell-isearch-cancel)
218 224
219(defvar-keymap eshell-hist-mode-map 225(defvar-keymap eshell-hist-mode-map
220 "<up>" #'eshell-previous-matching-input-from-input 226 "<up>" #'eshell-previous-matching-input-from-input
221 "<down>" #'eshell-next-matching-input-from-input 227 "<down>" #'eshell-next-matching-input-from-input
222 "C-<up>" #'eshell-previous-input 228 "C-<up>" #'eshell-previous-input
223 "C-<down>" #'eshell-next-input 229 "C-<down>" #'eshell-next-input
224 "M-r" #'eshell-previous-matching-input 230 "M-r" #'eshell-isearch-backward-regexp
225 "M-s" #'eshell-next-matching-input
226 "C-c M-r" #'eshell-previous-matching-input-from-input 231 "C-c M-r" #'eshell-previous-matching-input-from-input
227 "C-c M-s" #'eshell-next-matching-input-from-input 232 "C-c M-s" #'eshell-next-matching-input-from-input
228 "C-c C-l" #'eshell-list-history 233 "C-c C-l" #'eshell-list-history
@@ -261,20 +266,9 @@ Returns nil if INPUT is prepended by blank space, otherwise non-nil."
261 (not eshell-non-interactive-p)) 266 (not eshell-non-interactive-p))
262 (let ((rebind-alist eshell-rebind-keys-alist)) 267 (let ((rebind-alist eshell-rebind-keys-alist))
263 (setq-local eshell-rebind-keys-alist 268 (setq-local eshell-rebind-keys-alist
264 (append rebind-alist eshell-hist-rebind-keys-alist)) 269 (append rebind-alist eshell-hist-rebind-keys-alist)))
265 (setq-local search-invisible t)
266 (setq-local search-exit-option t)
267 (add-hook 'isearch-mode-hook
268 (lambda ()
269 (if (>= (point) eshell-last-output-end)
270 (setq overriding-terminal-local-map
271 eshell-isearch-map)))
272 nil t)
273 (add-hook 'isearch-mode-end-hook
274 (lambda ()
275 (setq overriding-terminal-local-map nil))
276 nil t))
277 (eshell-hist-mode)) 270 (eshell-hist-mode))
271 (add-hook 'isearch-mode-hook #'eshell--isearch-setup nil t)
278 272
279 (make-local-variable 'eshell-history-size) 273 (make-local-variable 'eshell-history-size)
280 (or eshell-history-size 274 (or eshell-history-size
@@ -384,6 +378,23 @@ unless a different file is specified on the command line.")
384 "Get an input line from the history ring." 378 "Get an input line from the history ring."
385 (ring-ref (or ring eshell-history-ring) index)) 379 (ring-ref (or ring eshell-history-ring) index))
386 380
381(defun eshell-goto-history (pos)
382 "Replace command line with the element at POS of history ring.
383Also update `eshell-history-index'. As a special case, if POS is nil
384and `eshell--stored-incomplete-input' is a non-empty string, restore the
385saved input."
386 (when (null eshell-history-index)
387 (setq eshell--stored-incomplete-input
388 (buffer-substring-no-properties eshell-last-output-end
389 (point-max))))
390 (setq eshell-history-index pos)
391 ;; Can't use kill-region as it sets this-command
392 (delete-region eshell-last-output-end (point-max))
393 (if (and pos (not (ring-empty-p eshell-history-ring)))
394 (insert-and-inherit (eshell-get-history pos))
395 (when (> (length eshell--stored-incomplete-input) 0)
396 (insert-and-inherit eshell--stored-incomplete-input))))
397
387(defun eshell-add-input-to-history (input) 398(defun eshell-add-input-to-history (input)
388 "Add the string INPUT to the history ring. 399 "Add the string INPUT to the history ring.
389Input is entered into the input history ring, if the value of 400Input is entered into the input history ring, if the value of
@@ -897,12 +908,12 @@ If N is negative, find the next or Nth next match."
897 ;; Has a match been found? 908 ;; Has a match been found?
898 (if (null pos) 909 (if (null pos)
899 (error "Not found") 910 (error "Not found")
900 (setq eshell-history-index pos) 911 (eshell-goto-history pos)
901 (unless (minibuffer-window-active-p (selected-window)) 912 (unless (or (minibuffer-window-active-p (selected-window))
902 (message "History item: %d" (- (ring-length eshell-history-ring) pos))) 913 ;; No messages for Isearch because it will show the
903 ;; Can't use kill-region as it sets this-command 914 ;; same messages (and more).
904 (delete-region eshell-last-output-end (point)) 915 isearch-mode)
905 (insert-and-inherit (eshell-get-history pos))))) 916 (message "History item: %d" (- (ring-length eshell-history-ring) pos))))))
906 917
907(defun eshell-next-matching-input (regexp arg) 918(defun eshell-next-matching-input (regexp arg)
908 "Search forwards through input history for match for REGEXP. 919 "Search forwards through input history for match for REGEXP.
@@ -937,114 +948,161 @@ If N is negative, search backwards for the -Nth previous match."
937 (interactive "p") 948 (interactive "p")
938 (eshell-previous-matching-input-from-input (- arg))) 949 (eshell-previous-matching-input-from-input (- arg)))
939 950
940(defun eshell-test-imatch () 951(defun eshell--isearch-setup ()
941 "If isearch match good, put point at the beginning and return non-nil." 952 "Set up Isearch to search the input history.
942 (if (get-text-property (point) 'history) 953Intended to be added to `isearch-mode-hook' in an Eshell buffer."
943 (progn (beginning-of-line) t) 954 (when (and
944 (let ((before (point))) 955 ;; Eshell is busy running a foreground process
945 (beginning-of-line) 956 (not eshell-foreground-command)
946 (if (and (not (bolp)) 957 (or eshell--force-history-isearch
947 (<= (point) before)) 958 (eq eshell-history-isearch t)
948 t 959 (and (eq eshell-history-isearch 'dwim)
949 (if isearch-forward 960 (>= (point) eshell-last-output-end))))
950 (progn 961 (setq isearch-message-prefix-add "history ")
951 (end-of-line) 962 (setq-local isearch-lazy-count nil)
952 (forward-char)) 963 (setq-local isearch-search-fun-function #'eshell-history-isearch-search
953 (beginning-of-line) 964 isearch-message-function #'eshell-history-isearch-message
954 (backward-char)))))) 965 isearch-wrap-function #'eshell-history-isearch-wrap
955 966 isearch-push-state-function #'eshell-history-isearch-push-state)
956(defun eshell-return-to-prompt () 967 (add-hook 'isearch-mode-end-hook #'eshell-history-isearch-end nil t)))
957 "Once a search string matches, insert it at the end and go there." 968
958 (setq isearch-other-end nil) 969(defun eshell-history-isearch-end ()
959 (let ((found (eshell-test-imatch)) before) 970 "Clean up after terminating history Isearch."
960 (while (and (not found) 971 (when (overlayp eshell--history-isearch-message-overlay)
961 (setq before 972 (delete-overlay eshell--history-isearch-message-overlay))
962 (funcall (if isearch-forward 973 (setq isearch-message-prefix-add nil)
963 're-search-forward 974 (kill-local-variable 'isearch-lazy-count)
964 're-search-backward) 975 (setq-local isearch-search-fun-function #'isearch-search-fun-default
965 isearch-string nil t))) 976 isearch-message-function nil
966 (setq found (eshell-test-imatch))) 977 isearch-wrap-function nil
967 (if (not found) 978 isearch-push-state-function nil)
968 (progn 979 (remove-hook 'isearch-mode-end-hook #'eshell-history-isearch-end t)
969 (goto-char eshell-last-output-end) 980 (setq isearch-opoint (point))
970 (delete-region (point) (point-max))) 981 (unless isearch-suspended
971 (setq before (point)) 982 (setq eshell--force-history-isearch nil)))
972 (let ((text (buffer-substring-no-properties 983
973 (point) (line-end-position))) 984(defun eshell-history-isearch-search ()
974 (orig (marker-position eshell-last-output-end))) 985 "Return search function for Isearch in input history."
975 (goto-char eshell-last-output-end) 986 (lambda (string bound noerror)
976 (delete-region (point) (point-max)) 987 (let ((search-fun (isearch-search-fun-default))
977 (when (and text (> (length text) 0)) 988 (found nil))
978 (insert text) 989 ;; Avoid highlighting matches in and before the last prompt
979 (put-text-property (1- (point)) (point) 990 (when (and bound isearch-forward
980 'last-search-pos before) 991 (< (point) eshell-last-output-end))
981 (set-marker eshell-last-output-end orig) 992 (goto-char eshell-last-output-end))
982 (goto-char eshell-last-output-end)))))) 993 (or
983 994 ;; First search in the initial input
984(defun eshell-prepare-for-search () 995 (funcall search-fun string
985 "Make sure the old history file is at the beginning of the buffer." 996 (if isearch-forward bound eshell-last-output-end)
986 (unless (get-text-property (point-min) 'history) 997 noerror)
987 (save-excursion 998 ;; Then search in the input history: put next/previous history
988 (goto-char (point-min)) 999 ;; element in the command line successively, then search the
989 (let ((end (copy-marker (point) t))) 1000 ;; string in the command line. Do this only when not
990 (insert-file-contents eshell-history-file-name) 1001 ;; lazy-highlighting (`bound' is nil).
991 (set-text-properties (point-min) end 1002 (unless bound
992 '(history t invisible t)))))) 1003 (condition-case nil
1004 (progn
1005 (while (not found)
1006 (cond (isearch-forward
1007 ;; Signal an error explicitly to break
1008 (when (or (null eshell-history-index)
1009 (eq eshell-history-index 0))
1010 (error "End of history; no next item"))
1011 (eshell-next-input 1)
1012 (goto-char eshell-last-output-end))
1013 (t
1014 ;; Signal an error explicitly to break
1015 (when (eq eshell-history-index
1016 (1- (ring-length eshell-history-ring)))
1017 (error "Beginning of history; no preceding item"))
1018 (eshell-previous-input 1)
1019 (goto-char (point-max))))
1020 (setq isearch-barrier (point)
1021 isearch-opoint (point))
1022 ;; After putting an history element in the command
1023 ;; line, search the string in them.
1024 (setq found (funcall search-fun string
1025 (unless isearch-forward
1026 eshell-last-output-end)
1027 noerror)))
1028 (point))
1029 ;; Return when no next/preceding element error signaled
1030 (error nil)))))))
1031
1032(defun eshell-history-isearch-message (&optional c-q-hack ellipsis)
1033 "Display the input history search prompt.
1034If there are no search errors, this function displays an overlay with
1035the Isearch prompt which replaces the original Eshell prompt.
1036Otherwise, it displays the standard Isearch message returned from the
1037function `isearch-message'."
1038 (if (not (and isearch-success (not isearch-error)))
1039 ;; Use standard message function (which displays a message in the
1040 ;; echo area) when not in command line, or search fails or has
1041 ;; errors (like incomplete regexp).
1042 (isearch-message c-q-hack ellipsis)
1043 ;; Otherwise, use an overlay over the Eshell prompt.
1044 (if (overlayp eshell--history-isearch-message-overlay)
1045 (move-overlay eshell--history-isearch-message-overlay
1046 (save-excursion
1047 (goto-char eshell-last-output-end)
1048 (forward-line 0)
1049 (point))
1050 eshell-last-output-end)
1051 (setq eshell--history-isearch-message-overlay
1052 (make-overlay (save-excursion
1053 (goto-char eshell-last-output-end)
1054 (forward-line 0)
1055 (point))
1056 eshell-last-output-end))
1057 (overlay-put eshell--history-isearch-message-overlay 'evaporate t))
1058 (overlay-put eshell--history-isearch-message-overlay
1059 'display (isearch-message-prefix ellipsis
1060 isearch-nonincremental))
1061 (if (and eshell-history-index (not ellipsis))
1062 (message "History item: %d" (- (ring-length eshell-history-ring)
1063 eshell-history-index))
1064 (message ""))))
1065
1066(defun eshell-history-isearch-wrap ()
1067 "Wrap the input history search."
1068 (if isearch-forward
1069 (eshell-goto-history (1- (ring-length eshell-history-ring)))
1070 (eshell-goto-history nil))
1071 (goto-char (if isearch-forward eshell-last-output-end (point-max))))
1072
1073(defun eshell-history-isearch-push-state ()
1074 "Save a function restoring the state of input history search.
1075Save `eshell-history-index' to the additional state parameter in the
1076search status stack."
1077 (let ((index eshell-history-index))
1078 (lambda (_cmd)
1079 (eshell-goto-history index))))
993 1080
994(defun eshell-isearch-backward (&optional invert) 1081(defun eshell-isearch-backward (&optional invert)
995 "Do incremental regexp search backward through past commands." 1082 "Do incremental search backward through past commands."
996 (interactive) 1083 (interactive nil eshell-mode)
997 (let ((inhibit-read-only t)) 1084 (setq eshell--force-history-isearch t)
998 (eshell-prepare-for-search) 1085 (if invert
999 (goto-char (point-max)) 1086 (isearch-forward nil t)
1000 (set-marker eshell-last-output-end (point)) 1087 (isearch-backward nil t)))
1001 (delete-region (point) (point-max)))
1002 (isearch-mode invert t 'eshell-return-to-prompt))
1003
1004(defun eshell-isearch-repeat-backward (&optional invert)
1005 "Do incremental regexp search backward through past commands."
1006 (interactive)
1007 (let ((old-pos (get-text-property (1- (point-max))
1008 'last-search-pos)))
1009 (when old-pos
1010 (goto-char old-pos)
1011 (if invert
1012 (end-of-line)
1013 (backward-char)))
1014 (setq isearch-forward invert)
1015 (isearch-search-and-update)))
1016 1088
1017(defun eshell-isearch-forward () 1089(defun eshell-isearch-forward ()
1018 "Do incremental regexp search backward through past commands." 1090 "Do incremental search forward through past commands."
1019 (interactive) 1091 (interactive nil eshell-mode)
1020 (eshell-isearch-backward t)) 1092 (eshell-isearch-backward t))
1021 1093
1022(defun eshell-isearch-repeat-forward () 1094(defun eshell-isearch-backward-regexp (&optional invert)
1023 "Do incremental regexp search backward through past commands." 1095 "Do incremental regexp search backward through past commands."
1024 (interactive) 1096 (interactive nil eshell-mode)
1025 (eshell-isearch-repeat-backward t)) 1097 (setq eshell--force-history-isearch t)
1026 1098 (if invert
1027(defun eshell-isearch-cancel () 1099 (isearch-forward-regexp nil t)
1028 (interactive) 1100 (isearch-backward-regexp nil t)))
1029 (goto-char eshell-last-output-end) 1101
1030 (delete-region (point) (point-max)) 1102(defun eshell-isearch-forward-regexp ()
1031 (call-interactively 'isearch-cancel)) 1103 "Do incremental regexp search forward through past commands."
1032 1104 (interactive nil eshell-mode)
1033(defun eshell-isearch-abort () 1105 (eshell-isearch-backward-regexp t))
1034 (interactive)
1035 (goto-char eshell-last-output-end)
1036 (delete-region (point) (point-max))
1037 (call-interactively 'isearch-abort))
1038
1039(defun eshell-isearch-delete-char ()
1040 (interactive)
1041 (save-excursion
1042 (isearch-delete-char)))
1043
1044(defun eshell-isearch-return ()
1045 (interactive)
1046 (isearch-done)
1047 (eshell-send-input))
1048 1106
1049(defun em-hist-unload-function () 1107(defun em-hist-unload-function ()
1050 (remove-hook 'kill-emacs-hook 'eshell-save-some-history)) 1108 (remove-hook 'kill-emacs-hook 'eshell-save-some-history))