Subject: bug#71545: 30.0.50; [PATCH] Fix paragraph navigation in Eshell

From: Jim Porter <[email-masked]>
Date: Thu, 13 Jun 2024 21:42:50 -0700

My previous patch had a typo that snuck in. Here's the fixed version.

From ac9aadef8863a2067202edc00501c34e22006222 Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Thu, 13 Jun 2024 21:26:53 -0700
Subject: [PATCH] Ensure navigating by paragraphs in Eshell stops at prompts
 and paragraphs

The previous implementation in 6ae2b74ed20 only stopped at prompts,
which isn't the right behavior.

* lisp/eshell/em-prompt.el (eshell-forward-paragraph)
(eshell-backward-paragraph): Reimplement to handle prompts and
paragraphs (the latter by calling the original 'forward-paragraph').

* test/lisp/eshell/em-prompt-tests.el
(em-prompt-test/next-previous-prompt/multiline): Rename.
(em-prompt-test/forward-backward-paragraph-1): New function.
(em-prompt-test/forward-backward-paragraph)
(em-prompt-test/forward-backward-paragraph/multiline): New tests.
---
 lisp/eshell/em-prompt.el            | 32 ++++++++++++++++---
 test/lisp/eshell/em-prompt-tests.el | 49 ++++++++++++++++++++++++++++-
 2 files changed, 75 insertions(+), 6 deletions(-)

diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index b6556d29544..7de2bd4dc21 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -167,17 +167,39 @@ eshell-backward-matching-input
 
 (defun eshell-forward-paragraph (&optional n)
   "Move to the beginning of the Nth next prompt in the buffer.
-Like `forward-paragraph', but navigates using fields."
+Like `forward-paragraph', but also stops at the beginning of each prompt."
   (interactive "p")
-  (eshell-next-prompt n)
-  (goto-char (field-beginning (point) t)))
+  (unless n (setq n 1))
+  (let (;; We'll handle the "paragraph" starts ourselves.
+        (paragraph-start regexp-unmatchable)
+        (inhibit-field-text-motion t))
+    (cond
+     ((> n 0)
+      (while (and (> n 0) (< (point) (point-max)))
+        (let ((next-paragraph (save-excursion (forward-paragraph) (point)))
+              (next-prompt (save-excursion
+                             (if-let ((match (text-property-search-forward
+                                              'field 'prompt t t)))
+                                 (prop-match-beginning match)
+                               (point-max)))))
+          (goto-char (min next-paragraph next-prompt)))
+        (setq n (1- n))))
+     ((< n 0)
+      (while (and (< n 0) (> (point) (point-min)))
+        (let ((prev-paragraph (save-excursion (backward-paragraph) (point)))
+              (prev-prompt (save-excursion
+                             (if (text-property-search-backward
+                                  'field 'prompt t)
+                                 (point)
+                               (point-min)))))
+          (goto-char (max prev-paragraph prev-prompt)))
+        (setq n (1+ n)))))))
 
 (defun eshell-backward-paragraph (&optional n)
   "Move to the beginning of the Nth previous prompt in the buffer.
 Like `backward-paragraph', but navigates using fields."
   (interactive "p")
-  (eshell-previous-prompt n)
-  (goto-char (field-beginning (point) t)))
+  (eshell-forward-paragraph (- (or n 1))))
 
 (defun eshell-next-prompt (&optional n)
   "Move to end of Nth next prompt in the buffer."
diff --git a/test/lisp/eshell/em-prompt-tests.el b/test/lisp/eshell/em-prompt-tests.el
index 964609e6410..fbadade061f 100644
--- a/test/lisp/eshell/em-prompt-tests.el
+++ b/test/lisp/eshell/em-prompt-tests.el
@@ -39,6 +39,9 @@ em-prompt-test--with-multiline
 
 ;;; Tests:
 
+
+;; Prompt output
+
 (ert-deftest em-prompt-test/field-properties ()
   "Check that field properties are properly set on Eshell output/prompts."
   (with-temp-eshell
@@ -104,6 +107,9 @@ em-prompt-test/after-failure
                'front-sticky '(read-only field font-lock-face)
                'rear-nonsticky '(read-only field font-lock-face)))))))
 
+
+;; Prompt navigation
+
 (defun em-prompt-test/next-previous-prompt-1 ()
   "Helper for checking forward/backward navigation of old prompts."
   (with-temp-eshell
@@ -150,11 +156,52 @@ em-prompt-test/next-previous-prompt
   "Check that navigating forward/backward through old prompts works correctly."
   (em-prompt-test/next-previous-prompt-1))
 
-(ert-deftest em-prompt-test/next-previous-prompt-multiline ()
+(ert-deftest em-prompt-test/next-previous-prompt/multiline ()
   "Check old prompt forward/backward navigation for multiline prompts."
   (em-prompt-test--with-multiline
    (em-prompt-test/next-previous-prompt-1)))
 
+(defun em-prompt-test/forward-backward-paragraph-1 ()
+  "Helper for checking forward/backward navigation by paragraphs."
+  (with-temp-eshell
+    (cl-flet ((at-prompt-for-command-p (command)
+                (and (equal (point) (field-beginning))
+                     (equal (get-text-property (point) 'field) 'prompt)
+                     (save-excursion
+                       (goto-char (field-end))
+                       (equal (field-string) command)))))
+      (eshell-insert-command "echo 'high five'")
+      (eshell-insert-command "echo 'up high\n\ndown low'")
+      (eshell-insert-command "echo 'too slow'")
+      (insert "echo goodby")            ; A partially-entered command.
+      (ert-info ("Go back to the last prompt")
+        (eshell-backward-paragraph)
+        (should (at-prompt-for-command-p "echo goodby")))
+      (ert-info ("Go back to the paragraph break")
+        (eshell-backward-paragraph 2)
+        (should (looking-at "\ndown low\n")))
+      (ert-info ("Go forward to the third prompt")
+        (eshell-forward-paragraph)
+        (should (at-prompt-for-command-p "echo 'too slow'\n")))
+      (ert-info ("Go backward to before the first prompt")
+        (eshell-backward-paragraph 5)
+        (should (looking-back "Welcome to the Emacs shell\n")))
+      (ert-info ("Go backward to the beginning of the buffer")
+        (eshell-backward-paragraph)
+        (should (bobp)))
+      (ert-info ("Go forward to the second prompt")
+        (eshell-forward-paragraph 3)
+        (should (at-prompt-for-command-p "echo 'up high\n\ndown low'\n"))))))
+
+(ert-deftest em-prompt-test/forward-backward-paragraph ()
+  "Check that navigating forward/backward through paragraphs works correctly."
+  (em-prompt-test/forward-backward-paragraph-1))
+
+(ert-deftest em-prompt-test/forward-backward-paragraph/multiline ()
+  "Check paragraph forward/backward navigation for multiline prompts."
+  (em-prompt-test--with-multiline
+   (em-prompt-test/forward-backward-paragraph-1)))
+
 (defun em-prompt-test/forward-backward-matching-input-1 ()
   "Helper for checking forward/backward navigation via regexps."
   (with-temp-eshell
-- 
2.25.1