From ac9aadef8863a2067202edc00501c34e22006222 Mon Sep 17 00:00:00 2001 From: Jim Porter 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