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
next prev parent 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).