unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
@ 2023-01-08 23:34 Jim Porter
  2023-01-09 12:09 ` Eli Zaretskii
  0 siblings, 1 reply; 11+ messages in thread
From: Jim Porter @ 2023-01-08 23:34 UTC (permalink / raw)
  To: 60666

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

This was prompted by a small annoyance I noticed in Eshell. When 
entering a command in an Eshell buffer, "C-a" takes you to the beginning 
of your input, but the Home key takes you all the way to the beginning 
of the line (so that the point is located within the the actual prompt 
text).

After digging through the Eshell code, I realized that this was because 
Eshell binds C-a to 'eshell-bol', but doesn't do the same for Home. The 
easiest way to fix this would be to add a binging for Home. However, 
when comparing this code to that of 'M-x shell', I noticed that the 
latter uses field properties to delimit parts of the shell buffer so 
that things Just Work. I think that's a more-robust way of doing things, 
and it would be nice to make Eshell do the same.

Attached are some patches to do this. Aside from the user-facing change 
that the Home key works like C-a now, this also means that users don't 
have to set 'eshell-prompt-regexp' anymore (unless they want to use 
paragraph navigation, that is). In fact, maybe it would be better to map 
M-{ and M-} to 'eshell-prompt-(previous|next)'. That would have the same 
effect as '(backward|forward)-paragraph' (I think), but it would be more 
robust.

Any thoughts here?

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

From 6ffe6c3c483c0efa3d0fb97c0a2f76def98d11dc 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 us remove
'eshell-bol'.

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

* 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.
---
 lisp/eshell/em-prompt.el            | 21 +++++---
 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 +++++++++++++++++++++++++++++
 5 files changed, 131 insertions(+), 24 deletions(-)
 create mode 100644 test/lisp/eshell/em-prompt-tests.el

diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index 575b5a595f1..866a21540d1 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)
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 f1297b82bee836ff50c8d7b922044157ba755752 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 866a21540d1..9fad36a3d5e 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: 5924 bytes --]

From 62a96c4065f49aa827a938c1b1638e3095eec268 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-skip-prompt): Make obsolete.

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

diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index 9fad36a3d5e..8e8dc21d6ab 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 ()
@@ -174,32 +176,31 @@ eshell-forward-matching-input
   (eshell-backward-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 "29.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..05cb92dada9 100644
--- a/test/lisp/eshell/em-prompt-tests.el
+++ b/test/lisp/eshell/em-prompt-tests.el
@@ -78,4 +78,22 @@ 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"))))
+
 ;;; em-prompt-tests.el ends here
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2023-01-14 19:30 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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
2023-01-13 13:07                 ` Eli Zaretskii
2023-01-14 19:30                   ` Jim Porter

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).