unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Pengji Zhang <me@pengjiz.com>
To: emacs-devel@gnu.org
Subject: Comint-like history search for Eshell?
Date: Sun, 03 Nov 2024 21:23:03 +0800	[thread overview]
Message-ID: <878qu01mfc.fsf@pengjiz.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 768 bytes --]

Hallo!

I hacked a small patch (attached) implementing a comint-like interface
for history search in Eshell, and I would like to know what people think
about it. You may type 'M-r' in Eshell to try the interface.

I myself always miss the 'M-r' in 'M-x shell' whenever I use 'M-r' in
Eshell. I tried 'eshell-isearch-{forward,backward}', but IMO they are a
bit rough -- they are implemented by inserting the whole history file
into the buffer and overriding Isearch keys.

This patch is mostly copied from comint.el (or simple.el), and it is
basically a proof of concept. Before I submit it to the bug tracker, I
would like to gather some input regarding the change. Thanks in advance!

Pengji

PS I am not subscribed to this list. Please CC me when replying. Thanks!


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: eshell-hist-isearch.diff --]
[-- Type: text/x-patch, Size: 10380 bytes --]

diff --git a/lisp/eshell/em-hist.el b/lisp/eshell/em-hist.el
index fffd611c06f..df95706cbe0 100644
--- a/lisp/eshell/em-hist.el
+++ b/lisp/eshell/em-hist.el
@@ -195,6 +195,10 @@ eshell-hist-rebind-keys-alist
 			       (repeat :inline t sexp))
 		       (function :tag "Command"))))
 
+(defcustom eshell-hist-isearch nil
+  "Non-nil to Isearch in input history only."
+  :type 'boolean)
+
 ;;; Internal Variables:
 
 (defvar eshell-history-ring nil)
@@ -205,24 +209,12 @@ eshell-hist--new-items
   "The number of new history items that have not been written to
 file.  This variable is local in each eshell buffer.")
 
-(defvar-keymap eshell-isearch-map
-  :doc "Keymap used in isearch in Eshell."
-  :parent isearch-mode-map
-  "C-m"         #'eshell-isearch-return
-  "C-r"         #'eshell-isearch-repeat-backward
-  "C-s"         #'eshell-isearch-repeat-forward
-  "C-g"         #'eshell-isearch-abort
-  "<backspace>" #'eshell-isearch-delete-char
-  "<delete>"    #'eshell-isearch-delete-char
-  "C-c C-c"     #'eshell-isearch-cancel)
-
 (defvar-keymap eshell-hist-mode-map
   "<up>"     #'eshell-previous-matching-input-from-input
   "<down>"   #'eshell-next-matching-input-from-input
   "C-<up>"   #'eshell-previous-input
   "C-<down>" #'eshell-next-input
-  "M-r"      #'eshell-previous-matching-input
-  "M-s"      #'eshell-next-matching-input
+  "M-r"      #'eshell-hist-isearch-backward-regexp
   "C-c M-r"  #'eshell-previous-matching-input-from-input
   "C-c M-s"  #'eshell-next-matching-input-from-input
   "C-c C-l"  #'eshell-list-history
@@ -258,23 +250,12 @@ eshell-hist-initialize
 	      #'eshell-complete-history-reference nil t))
 
   (if (and (eshell-using-module 'eshell-rebind)
-	   (not eshell-non-interactive-p))
+           (not eshell-non-interactive-p))
       (let ((rebind-alist eshell-rebind-keys-alist))
         (setq-local eshell-rebind-keys-alist
-	      (append rebind-alist eshell-hist-rebind-keys-alist))
-        (setq-local search-invisible t)
-        (setq-local search-exit-option t)
-	(add-hook 'isearch-mode-hook
-                  (lambda ()
-                    (if (>= (point) eshell-last-output-end)
-                        (setq overriding-terminal-local-map
-                              eshell-isearch-map)))
-                  nil t)
-	(add-hook 'isearch-mode-end-hook
-                  (lambda ()
-                    (setq overriding-terminal-local-map nil))
-                  nil t))
+                    (append rebind-alist eshell-hist-rebind-keys-alist)))
     (eshell-hist-mode))
+  (add-hook 'isearch-mode-hook #'eshell-hist--isearch-setup nil t)
 
   (make-local-variable 'eshell-history-size)
   (or eshell-history-size
@@ -901,7 +882,7 @@ eshell-previous-matching-input
       (unless (minibuffer-window-active-p (selected-window))
 	(message "History item: %d" (- (ring-length eshell-history-ring) pos)))
       ;; Can't use kill-region as it sets this-command
-      (delete-region eshell-last-output-end (point))
+      (delete-region eshell-last-output-end (point-max))
       (insert-and-inherit (eshell-get-history pos)))))
 
 (defun eshell-next-matching-input (regexp arg)
@@ -981,70 +962,121 @@ eshell-return-to-prompt
 	  (set-marker eshell-last-output-end orig)
 	  (goto-char eshell-last-output-end))))))
 
-(defun eshell-prepare-for-search ()
-  "Make sure the old history file is at the beginning of the buffer."
-  (unless (get-text-property (point-min) 'history)
-    (save-excursion
-      (goto-char (point-min))
-      (let ((end (copy-marker (point) t)))
-	(insert-file-contents eshell-history-file-name)
-	(set-text-properties (point-min) end
-			     '(history t invisible t))))))
-
-(defun eshell-isearch-backward (&optional invert)
-  "Do incremental regexp search backward through past commands."
-  (interactive)
-  (let ((inhibit-read-only t))
-    (eshell-prepare-for-search)
-    (goto-char (point-max))
-    (set-marker eshell-last-output-end (point))
-    (delete-region (point) (point-max)))
-  (isearch-mode invert t 'eshell-return-to-prompt))
-
-(defun eshell-isearch-repeat-backward (&optional invert)
-  "Do incremental regexp search backward through past commands."
-  (interactive)
-  (let ((old-pos (get-text-property (1- (point-max))
-				    'last-search-pos)))
-    (when old-pos
-      (goto-char old-pos)
-      (if invert
-	  (end-of-line)
-	(backward-char)))
-    (setq isearch-forward invert)
-    (isearch-search-and-update)))
+(defvar-local eshell-hist--isearch-message-overlay nil)
+
+(defun eshell-hist--isearch-setup ()
+  (when (and (>= (point) eshell-last-output-end)
+             eshell-hist-isearch)
+    (setq isearch-message-prefix-add "history ")
+    (setq-local isearch-lazy-count nil
+                isearch-search-fun-function #'eshell-hist--isearch-search
+                isearch-message-function #'eshell-hist--isearch-message
+                isearch-wrap-function #'eshell-hist--isearch-wrap
+                isearch-push-state-function #'eshell-hist--isearch-push-state)
+    (add-hook 'isearch-mode-end-hook #'eshell-hist--isearch-end nil t)))
+
+(defun eshell-hist--isearch-end ()
+  (when (overlayp eshell-hist--isearch-message-overlay)
+    (delete-overlay eshell-hist--isearch-message-overlay))
+  (setq isearch-message-prefix-add nil)
+  (setq-local isearch-search-fun-function #'isearch-search-fun-default
+              isearch-message-function nil
+              isearch-wrap-function nil
+              isearch-push-state-function nil)
+  (setq isearch-opoint (point))
+  (kill-local-variable 'isearch-lazy-count)
+  (remove-hook 'isearch-mode-end-hook #'eshell-hist--isearch-end t)
+  (unless isearch-suspended
+    (custom-reevaluate-setting 'eshell-hist-isearch)))
+
+(defun eshell-hist--isearch-search ()
+  (lambda (string bound noerror)
+    (let ((search-fun (isearch-search-fun-default))
+          found)
+      (when (and bound isearch-forward (< (point) eshell-last-output-end))
+        (goto-char eshell-last-output-end))
+      (or
+       (funcall search-fun string
+                (if isearch-forward bound (line-beginning-position))
+                noerror)
+       (unless bound
+         (condition-case nil
+             (progn
+               (while (not found)
+                 (cond (isearch-forward
+                        (when (or (null eshell-history-index)
+                                  (eq eshell-history-index 0))
+                          (error "End of history; no next item"))
+                        (eshell-next-input 1)
+                        (goto-char (line-beginning-position)))
+                       (t
+                        (when (eq eshell-history-index
+                                  (1- (ring-length eshell-history-ring)))
+                          (error "Beginning of history; no preceding item"))
+                        (eshell-previous-input 1)
+                        (goto-char (point-max))))
+                 (setq isearch-barrier (point) isearch-opoint (point))
+                 (setq found (funcall search-fun string
+                                      (unless isearch-forward
+                                        (line-beginning-position))
+                                      noerror)))
+               (point))
+           (error nil)))))))
+
+(defun eshell-hist--isearch-message (&optional c-q-hack ellipsis)
+  (if (not (and isearch-success (not isearch-error)))
+      (isearch-message c-q-hack ellipsis)
+    (if (overlayp eshell-hist--isearch-message-overlay)
+        (move-overlay eshell-hist--isearch-message-overlay
+                      (save-excursion
+                        (goto-char (line-beginning-position))
+                        (forward-line 0)
+                        (point))
+                      (line-beginning-position))
+      (setq eshell-hist--isearch-message-overlay
+            (make-overlay (save-excursion
+                            (goto-char (line-beginning-position))
+                            (forward-line 0)
+                            (point))
+                          (line-beginning-position)))
+      (overlay-put eshell-hist--isearch-message-overlay 'evaporate t))
+    (overlay-put eshell-hist--isearch-message-overlay
+                 'display (isearch-message-prefix ellipsis isearch-nonincremental))))
+
+(defun eshell-hist--isearch-wrap ()
+  (let ((pos (if isearch-forward
+                 (1- (ring-length eshell-history-ring))
+               0)))
+    (setq eshell-history-index pos)
+    (delete-region eshell-last-output-end (point-max))
+    (insert-and-inherit (eshell-get-history pos))
+    (goto-char (if isearch-forward (line-beginning-position) (point-max)))))
+
+(defun eshell-hist--isearch-push-state ()
+  (let ((index eshell-history-index))
+    (lambda (_)
+      (delete-region eshell-last-output-end (point-max))
+      (and index (insert-and-inherit (eshell-get-history index))))))
+
+(defun eshell-hist-isearch-backward-regexp ()
+  (interactive nil eshell-mode)
+  (setq eshell-hist-isearch t)
+  (isearch-backward-regexp nil t))
+
+(defun eshell-hist-isearch-forward-regexp ()
+  (interactive nil eshell-mode)
+  (setq eshell-hist-isearch t)
+  (isearch-forward-regexp nil t))
+
+(defun eshell-isearch-backward ()
+  (interactive nil eshell-mode)
+  (setq eshell-hist-isearch t)
+  (isearch-backward nil t))
 
 (defun eshell-isearch-forward ()
-  "Do incremental regexp search backward through past commands."
-  (interactive)
-  (eshell-isearch-backward t))
-
-(defun eshell-isearch-repeat-forward ()
-  "Do incremental regexp search backward through past commands."
-  (interactive)
-  (eshell-isearch-repeat-backward t))
-
-(defun eshell-isearch-cancel ()
-  (interactive)
-  (goto-char eshell-last-output-end)
-  (delete-region (point) (point-max))
-  (call-interactively 'isearch-cancel))
-
-(defun eshell-isearch-abort ()
-  (interactive)
-  (goto-char eshell-last-output-end)
-  (delete-region (point) (point-max))
-  (call-interactively 'isearch-abort))
-
-(defun eshell-isearch-delete-char ()
-  (interactive)
-  (save-excursion
-  (isearch-delete-char)))
-
-(defun eshell-isearch-return ()
-  (interactive)
-  (isearch-done)
-  (eshell-send-input))
+  (interactive nil eshell-mode)
+  (setq eshell-hist-isearch t)
+  (isearch-forward nil t))
 
 (defun em-hist-unload-function ()
   (remove-hook 'kill-emacs-hook 'eshell-save-some-history))

             reply	other threads:[~2024-11-03 13:23 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-11-03 13:23 Pengji Zhang [this message]
2024-11-03 22:45 ` Comint-like history search for Eshell? James Thomas
2024-11-03 23:09 ` Jim Porter
2024-11-03 23:28   ` Sean Whitton
2024-11-04  7:40   ` Juri Linkov
2024-11-04 11:47     ` Pengji Zhang
2024-11-04 19:12       ` Juri Linkov
2024-11-04  6:43 ` James Thomas
2024-11-08 21:02 ` James Thomas
2024-11-09 10:45   ` Pengji Zhang
2024-11-09 10:50   ` Pengji Zhang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=878qu01mfc.fsf@pengjiz.com \
    --to=me@pengjiz.com \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).