* 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
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
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
0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2023-01-09 12:09 UTC (permalink / raw)
To: Jim Porter; +Cc: 60666
> Date: Sun, 8 Jan 2023 15:34:03 -0800
> From: Jim Porter <jporterbugs@gmail.com>
>
> 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?
The only thought I have is "what if anyone _does_ like Home to do what
it does now?" So this change should have a NEWS ebtry about the
incompatible changes, and a recipe for how to get back the old
behavior for those who want it.
Thanks.
^ permalink raw reply [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-09 12:09 ` Eli Zaretskii
@ 2023-01-09 17:52 ` Jim Porter
2023-01-10 12:13 ` Eli Zaretskii
0 siblings, 1 reply; 11+ messages in thread
From: Jim Porter @ 2023-01-09 17:52 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 60666
[-- Attachment #1: Type: text/plain, Size: 759 bytes --]
On 1/9/2023 4:09 AM, Eli Zaretskii wrote:
> The only thought I have is "what if anyone _does_ like Home to do what
> it does now?" So this change should have a NEWS ebtry about the
> incompatible changes, and a recipe for how to get back the old
> behavior for those who want it.
Thanks, that makes sense. I've added a NEWS entry to the first part (see
attached). The recipe seems like it's on the edge of what would be
reasonable for users to paste into their configs though. I don't have a
strong opinion here, but if you think the recipe is too long, maybe we
could add the 'move-beginning-of-line-ignoring-fields' function to
simple.el. It might possibly be useful for some small number of people
who want to selectively disable field text motion.
[-- Attachment #2: 0001-Set-the-field-property-for-Eshell-output.patch --]
[-- Type: text/plain, Size: 12960 bytes --]
From d398a2ab7d031b18a633411311bb11323cdcb2c1 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.
* 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 | 15 ++++++
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 +++++++++++++++++++++++++++++
6 files changed, 146 insertions(+), 24 deletions(-)
create mode 100644 test/lisp/eshell/em-prompt-tests.el
diff --git a/etc/NEWS b/etc/NEWS
index 690e9c3faa9..ecb4d520896 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -100,6 +100,21 @@ 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:
+
+ (defun move-beginning-of-line-ignoring-fields (arg)
+ (interactive "^p")
+ (let ((inhibit-field-text-motion t))
+ (move-beginning-of-line arg)))
+
+ (keymap-set eshell-mode-map "<home>"
+ #'move-beginning-of-line-ignoring-fields)
+
+++
*** '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..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
^ permalink raw reply related [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-09 17:52 ` Jim Porter
@ 2023-01-10 12:13 ` Eli Zaretskii
2023-01-10 19:28 ` Jim Porter
0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2023-01-10 12:13 UTC (permalink / raw)
To: Jim Porter; +Cc: 60666
> Date: Mon, 9 Jan 2023 09:52:53 -0800
> Cc: 60666@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 1/9/2023 4:09 AM, Eli Zaretskii wrote:
> > The only thought I have is "what if anyone _does_ like Home to do what
> > it does now?" So this change should have a NEWS ebtry about the
> > incompatible changes, and a recipe for how to get back the old
> > behavior for those who want it.
>
> Thanks, that makes sense. I've added a NEWS entry to the first part (see
> attached). The recipe seems like it's on the edge of what would be
> reasonable for users to paste into their configs though. I don't have a
> strong opinion here, but if you think the recipe is too long, maybe we
> could add the 'move-beginning-of-line-ignoring-fields' function to
> simple.el. It might possibly be useful for some small number of people
> who want to selectively disable field text motion.
I don't think I understand why keeping eshell-bol and telling people
to bind <HOME> to that function if they want wouldn't be a much more
reasonable solution. I'm probably missing something.
Adding a function that is used in just two places doesn't sound TRT to
me.
^ permalink raw reply [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-10 12:13 ` Eli Zaretskii
@ 2023-01-10 19:28 ` Jim Porter
2023-01-10 20:11 ` Eli Zaretskii
0 siblings, 1 reply; 11+ messages in thread
From: Jim Porter @ 2023-01-10 19:28 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 60666
On 1/10/2023 4:13 AM, Eli Zaretskii wrote:
> I don't think I understand why keeping eshell-bol and telling people
> to bind <HOME> to that function if they want wouldn't be a much more
> reasonable solution. I'm probably missing something.
Do you mean that users who want <home> to go to the very beginning of
the line would bind it to 'eshell-bol'? If so, that wouldn't help.
'eshell-bol' moves the point to the beginning of the user's input, not
the actual beginning of the line.
If you mean the other way, and that users who want <home> to go to the
beginning of the input should just bind it to 'eshell-bol', that would
help, but then we wouldn't get the benefits of using 'field' properties
in Eshell. One of the nice things about using 'field' properties is that
'eshell-prompt-regexp' is no longer necessary[1]. That regexp is fairly
brittle. Also, users who customize their prompt need to make sure they
update it correctly.
Here's an example of the brittleness. Starting from "emacs -Q -f eshell":
~ $ mkdir "folder #1"
~ $ cd "folder #1"
~/folder #1 $ |
^-- Point is here
Now press C-a. Normally, that should take you to the beginning of the
input, but it takes you to the beginning of the whole line because the
prompt regexp isn't robust enough. Even worse, because Eshell can't tell
where the user's input starts, completion doesn't work on that line. (We
could probably fix this particular case by just tweaking
'eshell-prompt-regexp', but I don't think it could ever work in *all*
cases, whereas it shouldn't be hard to get perfection with 'field'
properties.)
Avoiding the need for 'eshell-prompt-regexp' also makes it simpler to
customize the Eshell prompt. Currently, it's just another thing to set,
and it's easy to get it wrong (it took me several tries to get it right
in my own configuration). The next patch I have planned for Eshell will
make it easier to customize the prompt[2], so getting rid of
'eshell-prompt-regexp' would be nice for any users who want to finally
try customizing the prompt.
[1] Except for using it to set 'paragraph-start' in Eshell.
[2] I plan to use 'format-mode-line' and provide some mode-line
constructs ("prompt constructs"?) for Eshell. Then users will have a
small toolbox of constructs they could use for their prompts.
^ permalink raw reply [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-10 19:28 ` Jim Porter
@ 2023-01-10 20:11 ` Eli Zaretskii
2023-01-10 20:51 ` Jim Porter
0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2023-01-10 20:11 UTC (permalink / raw)
To: Jim Porter; +Cc: 60666
> Date: Tue, 10 Jan 2023 11:28:17 -0800
> Cc: 60666@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 1/10/2023 4:13 AM, Eli Zaretskii wrote:
> > I don't think I understand why keeping eshell-bol and telling people
> > to bind <HOME> to that function if they want wouldn't be a much more
> > reasonable solution. I'm probably missing something.
>
> Do you mean that users who want <home> to go to the very beginning of
> the line would bind it to 'eshell-bol'? If so, that wouldn't help.
> 'eshell-bol' moves the point to the beginning of the user's input, not
> the actual beginning of the line.
That's not what I see in Emacs 29. C-a moves to the beginning of
input, i.e. it stops at the end of the prompt, whereas HOME goes all
the way to the beginning of screen line, including the prompt.
"C-h c" says that C-a is bound to eshell-bol, but HOME is bound to
move-beginning-of-line.
I'm asking why not let users have this distinction after your change,
with some easy customization?
> If you mean the other way, and that users who want <home> to go to the
> beginning of the input should just bind it to 'eshell-bol', that would
> help, but then we wouldn't get the benefits of using 'field' properties
> in Eshell. One of the nice things about using 'field' properties is that
> 'eshell-prompt-regexp' is no longer necessary[1]. That regexp is fairly
> brittle. Also, users who customize their prompt need to make sure they
> update it correctly.
I understand the benefits, but from user POV we just removed some
functionality as the price of making maintenance easier. That is not
a good balance for users who want the removed functionality.
^ permalink raw reply [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-10 20:11 ` Eli Zaretskii
@ 2023-01-10 20:51 ` Jim Porter
2023-01-11 12:15 ` Eli Zaretskii
0 siblings, 1 reply; 11+ messages in thread
From: Jim Porter @ 2023-01-10 20:51 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 60666
On 1/10/2023 12:11 PM, Eli Zaretskii wrote:
> That's not what I see in Emacs 29. C-a moves to the beginning of
> input, i.e. it stops at the end of the prompt, whereas HOME goes all
> the way to the beginning of screen line, including the prompt.
>
> "C-h c" says that C-a is bound to eshell-bol, but HOME is bound to
> move-beginning-of-line.
>
> I'm asking why not let users have this distinction after your change,
> with some easy customization?
It sounds like we agree but we're looking at this from different
directions. Since my patch adds the 'field' property, a user can't just
use 'move-beginning-of-line' to go to the very beginning of the line
(instead, it stops at the end of the prompt, where the field property ends).
I agree that an easy customization to restore the old behavior would be
nice. The old behavior has been around a long time, so I'm sure some
people have come to rely on it.
One option would be to add a new function like 'eshell-really-bol' (with
a better name, of course), that calls 'move-beginning-of-line' and
ignores fields, like the function I suggested in the NEWS entry. This
function wouldn't be used anywhere by default, but it makes it easy for
users to restore the behavior. This is probably the simplest, most
direct way.
Another option might be to enhance 'move-beginning-of-line'. Maybe we
could add an option so that calling it the first time obeys field
boundaries, but if you immediately call it again, it ignores them. Then,
a user who wants to go to the real beginning of a line can just press
<home> (or C-a) twice. That's not exactly the same as the old behavior,
but it would be usable outside of Eshell, and it's pretty close to
maintaining muscle memory: you just need to press the same key again.
Something like this might be sufficient, if we add a defcustom to toggle
this behavior:
----------------------------------------
diff --git a/lisp/simple.el b/lisp/simple.el
index 690968ca938..803c5ee07e4 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -8174,6 +8174,9 @@ move-beginning-of-line
(goto-char (previous-char-property-change (point)))
(goto-char (line-beginning-position))))
+ (let ((inhibit-field-text-motion
+ (if (eq last-command #'move-beginning-of-line)
+ t inhibit-field-text-motion)))
;; Now find first visible char in the line.
(while (and (< (point) orig) (invisible-p (point)))
(goto-char (next-char-property-change (point) orig)))
@@ -8189,7 +8192,7 @@ move-beginning-of-line
;; Otherwise, move to START with attention to fields.
;; (It is possible that fields never matter in this case.)
(constrain-to-field (point) orig
- (/= arg 1) t nil)))))
+ (/= arg 1) t nil))))))
;; Many people have said they rarely use this feature, and often type
----------------------------------------
The above is something I've occasionally wished for in other places. As
a workaround, I usually press "C-a C-b C-a" (or "<home> <left> <home>"),
but being able to press "C-a C-a" (or "<home> <home>") would be easier,
and probably more robust.
^ permalink raw reply related [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-10 20:51 ` Jim Porter
@ 2023-01-11 12:15 ` Eli Zaretskii
2023-01-12 18:10 ` Jim Porter
0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2023-01-11 12:15 UTC (permalink / raw)
To: Jim Porter; +Cc: 60666
> Date: Tue, 10 Jan 2023 12:51:14 -0800
> Cc: 60666@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 1/10/2023 12:11 PM, Eli Zaretskii wrote:
> > That's not what I see in Emacs 29. C-a moves to the beginning of
> > input, i.e. it stops at the end of the prompt, whereas HOME goes all
> > the way to the beginning of screen line, including the prompt.
> >
> > "C-h c" says that C-a is bound to eshell-bol, but HOME is bound to
> > move-beginning-of-line.
> >
> > I'm asking why not let users have this distinction after your change,
> > with some easy customization?
>
> It sounds like we agree but we're looking at this from different
> directions. Since my patch adds the 'field' property, a user can't just
> use 'move-beginning-of-line' to go to the very beginning of the line
> (instead, it stops at the end of the prompt, where the field property ends).
>
> I agree that an easy customization to restore the old behavior would be
> nice. The old behavior has been around a long time, so I'm sure some
> people have come to rely on it.
>
> One option would be to add a new function like 'eshell-really-bol' (with
> a better name, of course), that calls 'move-beginning-of-line' and
> ignores fields, like the function I suggested in the NEWS entry. This
> function wouldn't be used anywhere by default, but it makes it easy for
> users to restore the behavior. This is probably the simplest, most
> direct way.
I think 'eshell-really-bol' is the best alternative here, yes. Then
NEWS could tell users to bind it to HOME or whatever.
> Another option might be to enhance 'move-beginning-of-line'. Maybe we
> could add an option so that calling it the first time obeys field
> boundaries, but if you immediately call it again, it ignores them. Then,
> a user who wants to go to the real beginning of a line can just press
> <home> (or C-a) twice. That's not exactly the same as the old behavior,
> but it would be usable outside of Eshell, and it's pretty close to
> maintaining muscle memory: you just need to press the same key again.
Because this is not exactly the old behavior, I don't think it alone
can be the response to backward-compatibility need. It could be a
separate new feature, of course.
^ permalink raw reply [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-11 12:15 ` Eli Zaretskii
@ 2023-01-12 18:10 ` Jim Porter
2023-01-13 13:07 ` Eli Zaretskii
0 siblings, 1 reply; 11+ messages in thread
From: Jim Porter @ 2023-01-12 18:10 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 60666
[-- 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
^ permalink raw reply related [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-12 18:10 ` Jim Porter
@ 2023-01-13 13:07 ` Eli Zaretskii
2023-01-14 19:30 ` Jim Porter
0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2023-01-13 13:07 UTC (permalink / raw)
To: Jim Porter; +Cc: 60666
> Date: Thu, 12 Jan 2023 10:10:09 -0800
> Cc: 60666@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> 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.
Thanks, I'm happy now.
^ permalink raw reply [flat|nested] 11+ messages in thread
* bug#60666: 30.0.50; [PATCH] Use field properties in Eshell buffers
2023-01-13 13:07 ` Eli Zaretskii
@ 2023-01-14 19:30 ` Jim Porter
0 siblings, 0 replies; 11+ messages in thread
From: Jim Porter @ 2023-01-14 19:30 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 60666-done
On 1/13/2023 5:07 AM, Eli Zaretskii wrote:
> Thanks, I'm happy now.
Thanks. I've merged this as c257fd3a406d6aa83be60b96217e42b49b62cf5f.
Closing this bug now.
^ permalink raw reply [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).