unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: Jim Porter <jporterbugs@gmail.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 60666@debbugs.gnu.org
Subject: bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
Date: Thu, 12 Jan 2023 10:10:09 -0800	[thread overview]
Message-ID: <7c561016-1db1-30c8-d209-9845c924fcea@gmail.com> (raw)
In-Reply-To: <83mt6pmfmm.fsf@gnu.org>

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

On 1/11/2023 4:15 AM, Eli Zaretskii wrote:
> I think 'eshell-really-bol' is the best alternative here, yes.  Then
> NEWS could tell users to bind it to HOME or whatever.

Ok, done.

I also updated the implementation of 
'eshell-(forward|backward)-matching-input' to benefit from fields. (I 
didn't notice these functions earlier.) This reimplementation makes it 
work better than before, since you can now search for a regexp like 
"^echo" to find only inputs that *started* with "echo". Previously, that 
wasn't possible. I also added a regression test for it.

[-- Attachment #2: 0001-Set-the-field-property-for-Eshell-output.patch --]
[-- Type: text/plain, Size: 13297 bytes --]

From 57178d0e84f51de6f0f3814cfa887081d131c37d Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 8 Jan 2023 13:00:47 -0800
Subject: [PATCH 1/3] Set the 'field' property for Eshell output

This makes Eshell work more like 'M-x shell', and lets the <home> key
move to the beginning of the user's input at the prompt (bug#60666).

* lisp/eshell/em-prompt.el (eshell-emit-prompt): Add 'field' property
to prompt.
(eshell-bol-ignoring-prompt): New function.

* lisp/eshell/esh-io.el: Declare 'eshell-interactive-print'...
(eshell-output-object-to-target): ... use it.

* lisp/eshell/esh-mode.el (eshell-output-filter-functions): Update
docstring.
(eshell-interactive-print): Set the output to have a field value of
'command-output'.
(eshell-output-filter): Rename to...
(eshell-interactive-filter): ... this, and take a buffer instead of a
process.

* lisp/eshell/esh-proc.el (eshell-interactive-process-filter): New
function, adapted from 'eshell-output-filter'...
(eshell-gather-process-output): ... use it.

* test/lisp/eshell/em-prompt-tests.el: New file.

* etc/NEWS: Announce this change.
---
 etc/NEWS                            |  9 ++++
 lisp/eshell/em-prompt.el            | 29 ++++++++---
 lisp/eshell/esh-io.el               |  6 +--
 lisp/eshell/esh-mode.el             | 25 +++++----
 lisp/eshell/esh-proc.el             | 22 ++++++--
 test/lisp/eshell/em-prompt-tests.el | 81 +++++++++++++++++++++++++++++
 6 files changed, 148 insertions(+), 24 deletions(-)
 create mode 100644 test/lisp/eshell/em-prompt-tests.el

diff --git a/etc/NEWS b/etc/NEWS
index 690e9c3faa9..319169ba3a3 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -100,6 +100,15 @@ of arguments into a command, such as when defining aliases.  For more
 information, see the "(eshell) Dollars Expansion" node in the Eshell
 manual.
 
+---
+*** Eshell now uses 'field' properties in its output.
+In particular, this means that pressing the <home> key moves the point
+to the beginning of your input, not the beginning of the whole line.
+If you want to go back to the old behavior, add something like this to
+your configuration:
+
+    (keymap-set eshell-mode-map "<home>" #'eshell-bol-ignoring-prompt)
+
 +++
 *** 'eshell-read-aliases-list' is now an interactive command.
 After manually editing 'eshell-aliases-file', you can use this command
diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index 575b5a595f1..fdd16ca846a 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -134,14 +134,19 @@ eshell-emit-prompt
   (if (not eshell-prompt-function)
       (set-marker eshell-last-output-end (point))
     (let ((prompt (funcall eshell-prompt-function)))
-      (and eshell-highlight-prompt
-	   (add-text-properties 0 (length prompt)
-				'(read-only t
-				  font-lock-face eshell-prompt
-				  front-sticky (font-lock-face read-only)
-				  rear-nonsticky (font-lock-face read-only))
-				prompt))
-      (eshell-interactive-print prompt)))
+      (add-text-properties
+       0 (length prompt)
+       (if eshell-highlight-prompt
+           '( read-only t
+              field prompt
+              font-lock-face eshell-prompt
+              front-sticky (read-only field font-lock-face)
+              rear-nonsticky (read-only field font-lock-face))
+         '( field prompt
+            front-sticky (field)
+            rear-nonsticky (field)))
+       prompt)
+      (eshell-interactive-filter nil prompt)))
   (run-hooks 'eshell-after-prompt-hook))
 
 (defun eshell-backward-matching-input (regexp arg)
@@ -200,6 +205,14 @@ eshell-skip-prompt
 	     (<= (match-end 0) eol))
 	(goto-char (match-end 0)))))
 
+(defun eshell-bol-ignoring-prompt (arg)
+  "Move point to the beginning of the current line, past the prompt (if any).
+With argument ARG not nil or 1, move forward ARG - 1 lines
+first (see `move-beginning-of-line' for more information)."
+  (interactive "^p")
+  (let ((inhibit-field-text-motion t))
+    (move-beginning-of-line arg)))
+
 (provide 'em-prompt)
 
 ;; Local Variables:
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 4dad4c7429a..cccdb49ce2a 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -74,6 +74,8 @@
 (eval-when-compile
   (require 'cl-lib))
 
+(declare-function eshell-interactive-print "esh-mode" (string))
+
 (defgroup eshell-io nil
   "Eshell's I/O management code provides a scheme for treating many
 different kinds of objects -- symbols, files, buffers, etc. -- as
@@ -597,8 +599,6 @@ eshell-printn
   (eshell-print object)
   (eshell-print "\n"))
 
-(autoload 'eshell-output-filter "esh-mode")
-
 (defun eshell-output-object-to-target (object target)
   "Insert OBJECT into TARGET.
 Returns what was actually sent, or nil if nothing was sent."
@@ -608,7 +608,7 @@ eshell-output-object-to-target
 
    ((symbolp target)
     (if (eq target t)                   ; means "print to display"
-	(eshell-output-filter nil (eshell-stringify object))
+	(eshell-interactive-print (eshell-stringify object))
       (if (not (symbol-value target))
 	  (set target object)
 	(setq object (eshell-stringify object))
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index d80f1d1f390..97edc826c9a 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -155,7 +155,8 @@ eshell-output-filter-functions
     eshell-watch-for-password-prompt)
   "Functions to call before output is displayed.
 These functions are only called for output that is displayed
-interactively, and not for output which is redirected."
+interactively (see `eshell-interactive-filter'), and not for
+output which is redirected."
   :type 'hook)
 
 (defcustom eshell-preoutput-filter-functions nil
@@ -525,9 +526,13 @@ eshell-goto-input-start
 
 (custom-add-option 'eshell-pre-command-hook #'eshell-goto-input-start)
 
-(defsubst eshell-interactive-print (string)
+(defun eshell-interactive-print (string)
   "Print STRING to the eshell display buffer."
-  (eshell-output-filter nil string))
+  (when string
+    (add-text-properties 0 (length string)
+                         '(field command-output rear-nonsticky (field))
+                         string)
+    (eshell-interactive-filter nil string)))
 
 (defsubst eshell-begin-on-new-line ()
   "This function outputs a newline if not at beginning of line."
@@ -687,14 +692,14 @@ eshell-kill-new
 
 (custom-add-option 'eshell-input-filter-functions 'eshell-kill-new)
 
-(defun eshell-output-filter (process string)
-  "Send the output from PROCESS (STRING) to the interactive display.
+(defun eshell-interactive-filter (buffer string)
+  "Send output (STRING) to the interactive display, using BUFFER.
 This is done after all necessary filtering has been done."
-  (let ((oprocbuf (if process (process-buffer process)
-                    (current-buffer)))
-        (inhibit-modification-hooks t))
-    (when (and string oprocbuf (buffer-name oprocbuf))
-      (with-current-buffer oprocbuf
+  (unless buffer
+    (setq buffer (current-buffer)))
+  (when (and string (buffer-live-p buffer))
+    (let ((inhibit-modification-hooks t))
+      (with-current-buffer buffer
         (let ((functions eshell-preoutput-filter-functions))
           (while (and functions string)
             (setq string (funcall (car functions) string))
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 8a803c67e46..9bae812c922 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -304,7 +304,7 @@ eshell-gather-process-output
                :name (concat (file-name-nondirectory command) "-stderr")
                :buffer (current-buffer)
                :filter (if (eshell-interactive-output-p eshell-error-handle)
-                           #'eshell-output-filter
+                           #'eshell-interactive-process-filter
                          #'eshell-insertion-filter)
                :sentinel #'eshell-sentinel))
         (eshell-record-process-properties stderr-proc eshell-error-handle))
@@ -320,7 +320,7 @@ eshell-gather-process-output
                :buffer (current-buffer)
                :command (cons command args)
                :filter (if (eshell-interactive-output-p)
-                           #'eshell-output-filter
+                           #'eshell-interactive-process-filter
                          #'eshell-insertion-filter)
                :sentinel #'eshell-sentinel
                :connection-type conn-type
@@ -381,7 +381,7 @@ eshell-gather-process-output
 		  line (buffer-substring-no-properties lbeg lend))
 	    (set-buffer oldbuf)
 	    (if interact-p
-		(eshell-output-filter nil line)
+		(eshell-interactive-process-filter nil line)
 	      (eshell-output-object line))
 	    (setq lbeg lend)
 	    (set-buffer proc-buf))
@@ -402,6 +402,22 @@ eshell-gather-process-output
 	(setq proc t))))
     proc))
 
+(defun eshell-interactive-process-filter (process string)
+  "Send the output from PROCESS (STRING) to the interactive display.
+This is done after all necessary filtering has been done."
+  (when string
+    (add-text-properties 0 (length string)
+                         '(field command-output rear-nonsticky (field))
+                         string)
+    (require 'esh-mode)
+    (declare-function eshell-interactive-filter "esh-mode" (buffer string))
+    (eshell-interactive-filter (if process (process-buffer process)
+                                 (current-buffer))
+                               string)))
+
+(define-obsolete-function-alias 'eshell-output-filter
+  #'eshell-interactive-process-filter "30.1")
+
 (defun eshell-insertion-filter (proc string)
   "Insert a string into the eshell buffer, or a process/file/buffer.
 PROC is the process for which we're inserting output.  STRING is the
diff --git a/test/lisp/eshell/em-prompt-tests.el b/test/lisp/eshell/em-prompt-tests.el
new file mode 100644
index 00000000000..b67c74e86d0
--- /dev/null
+++ b/test/lisp/eshell/em-prompt-tests.el
@@ -0,0 +1,81 @@
+;;; em-prompt-tests.el --- em-prompt test suite  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for Eshell's prompt support.
+
+;;; Code:
+
+(require 'ert)
+(require 'eshell)
+(require 'em-prompt)
+
+(require 'eshell-tests-helpers
+         (expand-file-name "eshell-tests-helpers"
+                           (file-name-directory (or load-file-name
+                                                    default-directory))))
+
+;;; Tests:
+
+(ert-deftest em-prompt-test/field-properties ()
+  "Check that field properties are properly set on Eshell output/prompts."
+  (with-temp-eshell
+   (eshell-insert-command "echo hello")
+   (let ((last-prompt (field-string (1- eshell-last-input-start)))
+         (last-input  (field-string (1+ eshell-last-input-start)))
+         (last-output (field-string (1+ eshell-last-input-end))))
+     (should (equal-including-properties
+              last-prompt
+              (propertize
+               (format "%s $ " (directory-file-name default-directory))
+               'read-only t
+               'field 'prompt
+               'font-lock-face 'eshell-prompt
+               'front-sticky '(read-only field font-lock-face)
+               'rear-nonsticky '(read-only field font-lock-face))))
+     (should (equal last-input "echo hello\n"))
+     (should (equal-including-properties
+              last-output
+              (propertize "hello\n" 'rear-nonsticky '(field)
+                          'field 'command-output))))))
+
+(ert-deftest em-prompt-test/field-properties/no-highlight ()
+  "Check that field properties are properly set on Eshell output/prompts.
+This tests the case when `eshell-highlight-prompt' is nil."
+  (let ((eshell-highlight-prompt nil))
+    (with-temp-eshell
+     (eshell-insert-command "echo hello")
+     (let ((last-prompt (field-string (1- eshell-last-input-start)))
+           (last-input  (field-string (1+ eshell-last-input-start)))
+           (last-output (field-string (1+ eshell-last-input-end))))
+       (should (equal-including-properties
+                last-prompt
+                (propertize
+                 (format "%s $ " (directory-file-name default-directory))
+                 'field 'prompt
+                 'front-sticky '(field)
+                 'rear-nonsticky '(field))))
+       (should (equal last-input "echo hello\n"))
+       (should (equal-including-properties
+                last-output
+                (propertize "hello\n" 'rear-nonsticky '(field)
+                            'field 'command-output)))))))
+
+;;; em-prompt-tests.el ends here
-- 
2.25.1


[-- Attachment #3: 0002-Make-eshell-bol-obsolete.patch --]
[-- Type: text/plain, Size: 9675 bytes --]

From 9a5d1468c5acf8e655b480c6afa89ebf92fd05f7 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 8 Jan 2023 13:05:59 -0800
Subject: [PATCH 2/3] Make 'eshell-bol' obsolete

Now that Eshell uses fields for its output, 'eshell-bol' is no longer
needed, and we can just use 'beginning-of-line'.

* lisp/eshell/esh-mode.el (eshell-bol): Mark obsolete.
(eshell-mode-map): Remove 'C-a' mapping.
(eshell-command-map): Use 'move-beginning-of-line'.
(eshell-move-argument, eshell-kill-input): Use 'beginning-of-line'.
(eshell-get-old-input): Remove unnecessary call to
'eshell-skip-prompt-function'.

* lisp/eshell/em-rebind.el (eshell-rebind-keys-alist): Remove 'C-a'
and '<home>' mappings; the global mapping for these
('move-beginning-of-line') does the same thing now.

* lisp/eshell/em-cmpl.el (eshell-complete-parse-arguments):
* lisp/eshell/em-elecslash.el (eshell-electric-forward-slash):
* lisp/eshell/em-hist.el (eshell-hist-word-reference)
(eshell-previous-matching-input-from-input, eshell-test-imatch):
* lisp/eshell/em-prompt.el (eshell-backward-matching-input):
* lisp/eshell/em-rebind.el (eshell-point-within-input-p):
* test/lisp/eshell/eshell-tests.el (eshell-test/forward-arg): Use
'beginning-of-line'.

* test/lisp/eshell/eshell-tests.el (eshell-test/run-old-command):
Rename to...
(eshell-test/get-old-input): ... this, and expand the test.
---
 lisp/eshell/em-cmpl.el           |  2 +-
 lisp/eshell/em-elecslash.el      |  2 +-
 lisp/eshell/em-hist.el           |  6 +++---
 lisp/eshell/em-prompt.el         |  2 +-
 lisp/eshell/em-rebind.el         |  6 ++----
 lisp/eshell/esh-mode.el          | 31 +++++++++++++------------------
 test/lisp/eshell/eshell-tests.el | 15 ++++++++++-----
 7 files changed, 31 insertions(+), 33 deletions(-)

diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index 94ec5e8f1db..4206ad048fa 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -312,7 +312,7 @@ eshell-complete-parse-arguments
 	     (eshell-interactive-process-p))
     (eshell--pcomplete-insert-tab))
   (let ((end (point-marker))
-	(begin (save-excursion (eshell-bol) (point)))
+	(begin (save-excursion (beginning-of-line) (point)))
 	(posns (list t))
 	args delim)
     (when (and pcomplete-allow-modifications
diff --git a/lisp/eshell/em-elecslash.el b/lisp/eshell/em-elecslash.el
index 80bc0f031ef..2b003f58dc7 100644
--- a/lisp/eshell/em-elecslash.el
+++ b/lisp/eshell/em-elecslash.el
@@ -72,7 +72,7 @@ eshell-electric-forward-slash
     (delete-char -1)
     (let ((tilde-before (eq ?~ (char-before)))
           (command (save-excursion
-                     (eshell-bol)
+                     (beginning-of-line)
                      (skip-syntax-forward " ")
                      (thing-at-point 'sexp)))
           (prefix (file-remote-p default-directory)))
diff --git a/lisp/eshell/em-hist.el b/lisp/eshell/em-hist.el
index 05e9598f530..6e0e471d910 100644
--- a/lisp/eshell/em-hist.el
+++ b/lisp/eshell/em-hist.el
@@ -555,7 +555,7 @@ eshell-hist-word-reference
 (defun eshell-hist-parse-arguments (&optional b e)
   "Parse current command arguments in a history-code-friendly way."
   (let ((end (or e (point)))
-	(begin (or b (save-excursion (eshell-bol) (point))))
+	(begin (or b (save-excursion (beginning-of-line) (point))))
 	(posb (list t))
 	(pose (list t))
 	(textargs (list t))
@@ -913,7 +913,7 @@ eshell-previous-matching-input-from-input
 				eshell-next-matching-input-from-input)))
       ;; Starting a new search
       (setq eshell-matching-input-from-input-string
-	    (buffer-substring (save-excursion (eshell-bol) (point))
+	    (buffer-substring (save-excursion (beginning-of-line) (point))
 			      (point))
 	    eshell-history-index nil))
   (eshell-previous-matching-input
@@ -933,7 +933,7 @@ eshell-test-imatch
   (if (get-text-property (point) 'history)
       (progn (beginning-of-line) t)
     (let ((before (point)))
-      (eshell-bol)
+      (beginning-of-line)
       (if (and (not (bolp))
 	       (<= (point) before))
 	  t
diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index fdd16ca846a..5a89ff35a2b 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -163,7 +163,7 @@ eshell-backward-matching-input
 	(progn (message "Not found")
 	       (ding))
       (goto-char pos)
-      (eshell-bol))))
+      (beginning-of-line))))
 
 (defun eshell-forward-matching-input (regexp arg)
   "Search forward through buffer for match for REGEXP.
diff --git a/lisp/eshell/em-rebind.el b/lisp/eshell/em-rebind.el
index 2c95d4fdffb..f147d432300 100644
--- a/lisp/eshell/em-rebind.el
+++ b/lisp/eshell/em-rebind.el
@@ -50,9 +50,7 @@ eshell-rebind-load-hook
   :group 'eshell-rebind)
 
 (defcustom eshell-rebind-keys-alist
-  '(([(control ?a)] . eshell-bol)
-    ([home]         . eshell-bol)
-    ([(control ?d)] . eshell-delchar-or-maybe-eof)
+  '(([(control ?d)] . eshell-delchar-or-maybe-eof)
     ([backspace]    . eshell-delete-backward-char)
     ([delete]       . eshell-delete-backward-char)
     ([(control ?w)] . backward-kill-word)
@@ -190,7 +188,7 @@ eshell-point-within-input-p
 	(and eshell-remap-previous-input
 	     (setq begin
 		   (save-excursion
-		     (eshell-bol)
+		     (beginning-of-line)
 		     (and (not (bolp)) (point))))
 	     (>= pos begin)
 	     (<= pos (line-end-position))
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index 97edc826c9a..90e003d188b 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -262,14 +262,13 @@ eshell-mode-map
   "C-c"   'eshell-command-map
   "RET"   #'eshell-send-input
   "M-RET" #'eshell-queue-input
-  "C-M-l" #'eshell-show-output
-  "C-a"   #'eshell-bol)
+  "C-M-l" #'eshell-show-output)
 
 (defvar-keymap eshell-command-map
   :prefix 'eshell-command-map
   "M-o" #'eshell-mark-output
   "M-d" #'eshell-toggle-direct-send
-  "C-a" #'eshell-bol
+  "C-a" #'move-beginning-of-line
   "C-b" #'eshell-backward-argument
   "C-e" #'eshell-show-maximum-output
   "C-f" #'eshell-forward-argument
@@ -472,7 +471,7 @@ eshell-find-tag
 (defun eshell-move-argument (limit func property arg)
   "Move forward ARG arguments."
   (catch 'eshell-incomplete
-    (eshell-parse-arguments (save-excursion (eshell-bol) (point))
+    (eshell-parse-arguments (save-excursion (beginning-of-line) (point))
 			    (line-end-position)))
   (let ((pos (save-excursion
 	       (funcall func 1)
@@ -505,12 +504,7 @@ eshell-repeat-argument
     (kill-ring-save begin (point))
     (yank)))
 
-(defun eshell-bol ()
-  "Go to the beginning of line, then skip past the prompt, if any."
-  (interactive)
-  (beginning-of-line)
-  (and eshell-skip-prompt-function
-       (funcall eshell-skip-prompt-function)))
+(define-obsolete-function-alias 'eshell-bol #'beginning-of-line "30.1")
 
 (defsubst eshell-push-command-mark ()
   "Push a mark at the end of the last input text."
@@ -856,7 +850,7 @@ eshell-kill-input
   (if (> (point) eshell-last-output-end)
       (kill-region eshell-last-output-end (point))
     (let ((here (point)))
-      (eshell-bol)
+      (beginning-of-line)
       (kill-region (point) here))))
 
 (defun eshell-show-maximum-output (&optional interactive)
@@ -884,17 +878,18 @@ eshell/clear-scrollback
     (erase-buffer)))
 
 (defun eshell-get-old-input (&optional use-current-region)
-  "Return the command input on the current line."
+  "Return the command input on the current line.
+If USE-CURRENT-REGION is non-nil, return the current region."
   (if use-current-region
       (buffer-substring (min (point) (mark))
 			(max (point) (mark)))
     (save-excursion
-      (beginning-of-line)
-      (and eshell-skip-prompt-function
-	   (funcall eshell-skip-prompt-function))
-      (let ((beg (point)))
-	(end-of-line)
-	(buffer-substring beg (point))))))
+      (let ((inhibit-field-text-motion t))
+        (end-of-line))
+      (let ((inhibit-field-text-motion)
+            (end (point)))
+        (beginning-of-line)
+        (buffer-substring (point) end)))))
 
 (defun eshell-copy-old-input ()
   "Insert after prompt old input at point as new input to be edited."
diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el
index be968e1558f..0b0e0701cb4 100644
--- a/test/lisp/eshell/eshell-tests.el
+++ b/test/lisp/eshell/eshell-tests.el
@@ -117,14 +117,14 @@ eshell-test/forward-arg
   (with-temp-eshell
    (eshell-insert-command "echo $(+ 1 (- 4 3)) \"alpha beta\" file" 'ignore)
    (let ((here (point)) begin valid)
-     (eshell-bol)
+     (beginning-of-line)
      (setq begin (point))
      (eshell-forward-argument 4)
      (setq valid (= here (point)))
      (eshell-backward-argument 4)
      (prog1
          (and valid (= begin (point)))
-       (eshell-bol)
+       (beginning-of-line)
        (delete-region (point) (point-max))))))
 
 (ert-deftest eshell-test/queue-input ()
@@ -148,12 +148,17 @@ eshell-test/flush-output
    (should (eshell-match-output
             (concat "^" (regexp-quote "*** output flushed ***\n") "$")))))
 
-(ert-deftest eshell-test/run-old-command ()
-  "Re-run an old command"
+(ert-deftest eshell-test/get-old-input ()
+  "Test that we can get the input of a previous command."
   (with-temp-eshell
    (eshell-insert-command "echo alpha")
    (goto-char eshell-last-input-start)
-   (string= (eshell-get-old-input) "echo alpha")))
+   (should (string= (eshell-get-old-input) "echo alpha"))
+   ;; Make sure that `eshell-get-old-input' works even if the point is
+   ;; inside the prompt.
+   (let ((inhibit-field-text-motion t))
+     (beginning-of-line))
+   (should (string= (eshell-get-old-input) "echo alpha")))))
 
 (provide 'eshell-tests)
 
-- 
2.25.1


[-- Attachment #4: 0003-Use-the-field-property-to-navigate-through-Eshell-pr.patch --]
[-- Type: text/plain, Size: 8917 bytes --]

From 53132f89d1ee5c0f233fe334f488232bc53964c0 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 8 Jan 2023 13:50:50 -0800
Subject: [PATCH 3/3] Use the 'field' property to navigate through Eshell
 prompts

* lisp/eshell/esh-mode.el (eshell-skip-prompt-function): Make
obsolete.

* lisp/eshell/em-prompt.el (eshell-prompt-regexp): Update docstring.
(eshell-prompt-initialize): Don't set 'eshell-skip-prompt-function'.
(eshell-next-prompt): Search for the 'field' property set to 'prompt'
to find the next prompt.
(eshell-previous-prompt): Move 'forward-line' call into
'eshell-next-prompt'.
(eshell-forward-matching-input, eshell-backward-matching-input):
Reimplement on top of 'eshell-next-prompt'.
(eshell-skip-prompt): Make obsolete.

* test/lisp/eshell/em-prompt-tests.el
(em-prompt-test/next-previous-prompt): New test.
---
 lisp/eshell/em-prompt.el            | 88 ++++++++++++++---------------
 lisp/eshell/esh-mode.el             |  2 +
 test/lisp/eshell/em-prompt-tests.el | 37 ++++++++++++
 3 files changed, 83 insertions(+), 44 deletions(-)

diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index 5a89ff35a2b..52d46282c52 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -29,6 +29,8 @@
 (require 'esh-mode)
 (eval-when-compile (require 'eshell))
 
+(require 'text-property-search)
+
 ;;;###autoload
 (progn
 (defgroup eshell-prompt nil
@@ -58,11 +60,12 @@ eshell-prompt-function
   :group 'eshell-prompt)
 
 (defcustom eshell-prompt-regexp "^[^#$\n]* [#$] "
-  "A regexp which fully matches your eshell prompt.
-This setting is important, since it affects how eshell will interpret
-the lines that are passed to it.
-If this variable is changed, all Eshell buffers must be exited and
-re-entered for it to take effect."
+  "A regexp which fully matches your Eshell prompt.
+This is useful for navigating by paragraph using \
+\\[forward-paragraph] and \\[backward-paragraph].
+
+If this variable is changed, all Eshell buffers must be exited
+and re-entered for it to take effect."
   :type 'regexp
   :group 'eshell-prompt)
 
@@ -123,7 +126,6 @@ eshell-prompt-initialize
     (if eshell-prompt-regexp
         (setq-local paragraph-start eshell-prompt-regexp))
 
-    (setq-local eshell-skip-prompt-function #'eshell-skip-prompt)
     (eshell-prompt-mode)))
 
 (defun eshell-emit-prompt ()
@@ -149,57 +151,55 @@ eshell-emit-prompt
       (eshell-interactive-filter nil prompt)))
   (run-hooks 'eshell-after-prompt-hook))
 
-(defun eshell-backward-matching-input (regexp arg)
-  "Search backward through buffer for match for REGEXP.
-Matches are searched for on lines that match `eshell-prompt-regexp'.
-With prefix argument N, search for Nth previous match.
-If N is negative, find the next or Nth next match."
-  (interactive (eshell-regexp-arg "Backward input matching (regexp): "))
-  (let* ((re (concat eshell-prompt-regexp ".*" regexp))
-	 (pos (save-excursion (end-of-line (if (> arg 0) 0 1))
-			      (if (re-search-backward re nil t arg)
-				  (point)))))
-    (if (null pos)
-	(progn (message "Not found")
-	       (ding))
-      (goto-char pos)
-      (beginning-of-line))))
-
 (defun eshell-forward-matching-input (regexp arg)
-  "Search forward through buffer for match for REGEXP.
-Matches are searched for on lines that match `eshell-prompt-regexp'.
-With prefix argument N, search for Nth following match.
-If N is negative, find the previous or Nth previous match."
+  "Search forward through buffer for command input that matches REGEXP.
+With prefix argument N, search for Nth next match.  If N is
+negative, find the Nth previous match."
   (interactive (eshell-regexp-arg "Forward input matching (regexp): "))
-  (eshell-backward-matching-input regexp (- arg)))
+  (let ((direction (if (> arg 0) 1 -1))
+        (count (abs arg)))
+    (unless (catch 'found
+              (while (> count 0)
+                (eshell-next-prompt direction)
+                (when (and (string-match regexp (field-string))
+                           (= (setq count (1- count)) 0))
+                  (throw 'found t))))
+      (message "Not found")
+      (ding))))
+
+(defun eshell-backward-matching-input (regexp arg)
+  "Search backward through buffer for command input that matches REGEXP.
+With prefix argument N, search for Nth previous match.  If N is
+negative, find the Nth next match."
+  (interactive (eshell-regexp-arg "Backward input matching (regexp): "))
+  (eshell-forward-matching-input regexp (- arg)))
 
 (defun eshell-next-prompt (n)
-  "Move to end of Nth next prompt in the buffer.
-See `eshell-prompt-regexp'."
+  "Move to end of Nth next prompt in the buffer."
   (interactive "p")
-  (if eshell-highlight-prompt
-      (progn
-        (while (< n 0)
-          (while (and (re-search-backward eshell-prompt-regexp nil t)
-                      (not (get-text-property (match-beginning 0) 'read-only))))
-          (setq n (1+ n)))
-        (while (> n 0)
-          (while (and (re-search-forward eshell-prompt-regexp nil t)
-                      (not (get-text-property (match-beginning 0) 'read-only))))
-          (setq n (1- n))))
-    (re-search-forward eshell-prompt-regexp nil t n))
-  (eshell-skip-prompt))
+  (if (natnump n)
+      (while (and (> n 0)
+                  (text-property-search-forward 'field 'prompt t))
+        (setq n (1- n)))
+    (let (match this-match)
+      (forward-line 0)           ; Don't count prompt on current line.
+      (while (and (< n 0)
+                  (setq this-match (text-property-search-backward
+                                    'field 'prompt t)))
+        (setq match this-match
+              n (1+ n)))
+      (when match
+        (goto-char (prop-match-end match))))))
 
 (defun eshell-previous-prompt (n)
-  "Move to end of Nth previous prompt in the buffer.
-See `eshell-prompt-regexp'."
+  "Move to end of Nth previous prompt in the buffer."
   (interactive "p")
-  (forward-line 0)            ; Don't count prompt on current line.
   (eshell-next-prompt (- n)))
 
 (defun eshell-skip-prompt ()
   "Skip past the text matching regexp `eshell-prompt-regexp'.
 If this takes us past the end of the current line, don't skip at all."
+  (declare (obsolete nil "30.1"))
   (let ((eol (line-end-position)))
     (if (and (looking-at eshell-prompt-regexp)
 	     (<= (match-end 0) eol))
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index 90e003d188b..50a87e9bb9b 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -176,6 +176,8 @@ eshell-skip-prompt-function
   "A function called from beginning of line to skip the prompt."
   :type '(choice (const nil) function))
 
+(make-obsolete-variable 'eshell-skip-prompt-function nil "29.1")
+
 (defcustom eshell-status-in-mode-line t
   "If non-nil, let the user know a command is running in the mode line."
   :type 'boolean)
diff --git a/test/lisp/eshell/em-prompt-tests.el b/test/lisp/eshell/em-prompt-tests.el
index b67c74e86d0..ea65b9e7ac2 100644
--- a/test/lisp/eshell/em-prompt-tests.el
+++ b/test/lisp/eshell/em-prompt-tests.el
@@ -78,4 +78,41 @@ em-prompt-test/field-properties/no-highlight
                 (propertize "hello\n" 'rear-nonsticky '(field)
                             'field 'command-output)))))))
 
+(ert-deftest em-prompt-test/next-previous-prompt ()
+  "Check that navigating forward/backward through old prompts works correctly."
+  (with-temp-eshell
+   (eshell-insert-command "echo one")
+   (eshell-insert-command "echo two")
+   (eshell-insert-command "echo three")
+   (insert "echo fou")                  ; A partially-entered command.
+   ;; Go back one prompt.
+   (eshell-previous-prompt 1)
+   (should (equal (eshell-get-old-input) "echo three"))
+   ;; Go back two prompts, starting from the end of this line.
+   (end-of-line)
+   (eshell-previous-prompt 2)
+   (should (equal (eshell-get-old-input) "echo one"))
+   ;; Go forward three prompts.
+   (eshell-next-prompt 3)
+   (should (equal (eshell-get-old-input) "echo fou"))))
+
+(ert-deftest em-prompt-test/forward-backward-matching-input ()
+  "Check that navigating forward/backward via regexps works correctly."
+  (with-temp-eshell
+   (eshell-insert-command "echo one")
+   (eshell-insert-command "printnl something else")
+   (eshell-insert-command "echo two")
+   (eshell-insert-command "echo three")
+   (insert "echo fou")                  ; A partially-entered command.
+   ;; Go back one prompt.
+   (eshell-backward-matching-input "echo" 1)
+   (should (equal (eshell-get-old-input) "echo three"))
+   ;; Go back two prompts, starting from the end of this line.
+   (end-of-line)
+   (eshell-backward-matching-input "echo" 2)
+   (should (equal (eshell-get-old-input) "echo one"))
+   ;; Go forward three prompts.
+   (eshell-forward-matching-input "echo" 3)
+   (should (equal (eshell-get-old-input) "echo fou"))))
+
 ;;; em-prompt-tests.el ends here
-- 
2.25.1


  reply	other threads:[~2023-01-12 18:10 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-08 23:34 bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers Jim Porter
2023-01-09 12:09 ` Eli Zaretskii
2023-01-09 17:52   ` Jim Porter
2023-01-10 12:13     ` Eli Zaretskii
2023-01-10 19:28       ` Jim Porter
2023-01-10 20:11         ` Eli Zaretskii
2023-01-10 20:51           ` Jim Porter
2023-01-11 12:15             ` Eli Zaretskii
2023-01-12 18:10               ` Jim Porter [this message]
2023-01-13 13:07                 ` Eli Zaretskii
2023-01-14 19:30                   ` Jim Porter

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=7c561016-1db1-30c8-d209-9845c924fcea@gmail.com \
    --to=jporterbugs@gmail.com \
    --cc=60666@debbugs.gnu.org \
    --cc=eliz@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).