emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master 558f04c39e 1/3: Set the 'field' property for Eshell output


From: Jim Porter
Subject: master 558f04c39e 1/3: Set the 'field' property for Eshell output
Date: Sat, 14 Jan 2023 14:30:12 -0500 (EST)

branch: master
commit 558f04c39e036d2f681f72556627768d7bee9ab5
Author: Jim Porter <jporterbugs@gmail.com>
Commit: Jim Porter <jporterbugs@gmail.com>

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

diff --git a/etc/NEWS b/etc/NEWS
index cb83ec24a6..068f7a27db 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -127,6 +127,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 575b5a595f..fdd16ca846 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -134,14 +134,19 @@ arriving, or after."
   (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 @@ If this takes us past the end of the current line, don't 
skip at all."
             (<= (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 4dad4c7429..cccdb49ce2 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 @@ after all printing is over with no argument."
   (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 @@ Returns what was actually sent, or nil if nothing was sent."
 
    ((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 d80f1d1f39..97edc826c9 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -155,7 +155,8 @@ number, if the function `eshell-truncate-buffer' is on
     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 @@ Putting this function on `eshell-pre-command-hook' will 
mimic Plan 9's
 
 (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 @@ newline."
 
 (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 8a803c67e4..9bae812c92 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -304,7 +304,7 @@ Used only on systems which do not support async 
subprocesses.")
                :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 @@ Used only on systems which do not support async 
subprocesses.")
                :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 @@ Used only on systems which do not support async 
subprocesses.")
                  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 @@ Used only on systems which do not support async 
subprocesses.")
        (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 0000000000..695cb1bab2
--- /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) 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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]