Subject: [PATCH] Display the exit code if the last command failed in Eshell
From: Davide Masserut <[email-masked]>
To: [email-masked]
Date: Wed, 30 Aug 2023 00:24:28 +0200
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

There are commands that fail without printing any messages, but 
set specific error codes.

This patch extends the default prompt function to show the exit 
code of the previous failed command.

Before:

~ $ false
~ $

After:

~ $ false
~ [1] $

I believe this is a good default, since it is displayed only when 
a error occurs and hopefully makes debugging easier by showing the 
error code without further input.

In GNU Emacs 30.0.50 (build 10, x86_64-pc-linux-gnu, GTK+ Version
 3.24.38, cairo version 1.17.8) of 2023-08-29 built on T480s
Repository revision: ed77dc17f657d99ccf23778c14f06f7226f478f0
Repository branch: master
System Description: Arch Linux

Configured using:
 'configure -C --prefix /home/davide/.local --with-pgtk
 --with-native-compilation --enable-link-time-optimization
 --enable-locallisppath=/usr/share/emacs/site-lisp/
 'CFLAGS=-march=native -O2''
From a0e2f7bda33767e8ce104dd32c76e4d3d6c1a2f8 Mon Sep 17 00:00:00 2001
From: Davide Masserut <[email-masked]>
Date: Tue, 29 Aug 2023 22:33:48 +0200
Subject: [PATCH] Display the exit code if the last command failed in Eshell

* etc/NEWS: Announce change.
* lisp/eshell/em-prompt.el (eshell-prompt-function): Insert the exit
code if last command failed.
* lisp/eshell/esh-io.el (eshell-last-command-status): Make it
buffer-local.
* test/lisp/eshell/em-prompt-tests.el (em-prompt-test/after-failure)
(em-prompt-test/next-previous-prompt-with)
(em-prompt-test/forward-backward-matching-input-with): New tests.
---
 etc/NEWS                            |  3 +++
 lisp/eshell/em-prompt.el            |  2 ++
 lisp/eshell/esh-io.el               |  2 +-
 test/lisp/eshell/em-prompt-tests.el | 37 +++++++++++++++++++++++++----
 4 files changed, 39 insertions(+), 5 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 9a98db8c83a..9622e57f476 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -370,6 +370,9 @@ to load the edited aliases.
 Running 'rgrep' in Eshell now uses the Emacs grep facility instead of
 calling external rgrep.
 
+---
+*** The Eshell prompt now shows the exit code if the last command failed.
+
 ** Pcomplete
 
 ---
diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index 42f8f273b52..692b579d02d 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -50,6 +50,8 @@ eshell-prompt-load-hook
 (defcustom eshell-prompt-function
   (lambda ()
     (concat (abbreviate-file-name (eshell/pwd))
+            (unless (eshell-exit-success-p)
+              (format " [%d]" eshell-last-command-status))
             (if (= (file-user-uid) 0) " # " " $ ")))
   "A function that returns the Eshell prompt string.
 Make sure to update `eshell-prompt-regexp' so that it will match your
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index c07f871dd37..cd0cee6e21d 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -170,7 +170,7 @@ eshell-redirection-operators-alist
 
 (defvar eshell-current-handles nil)
 
-(defvar eshell-last-command-status 0
+(defvar-local eshell-last-command-status 0
   "The exit code from the last command.  0 if successful.")
 
 (defvar eshell-last-command-result nil
diff --git a/test/lisp/eshell/em-prompt-tests.el b/test/lisp/eshell/em-prompt-tests.el
index 93bf9d84ab3..c90f417cefd 100644
--- a/test/lisp/eshell/em-prompt-tests.el
+++ b/test/lisp/eshell/em-prompt-tests.el
@@ -34,6 +34,25 @@
 
 ;;; Tests:
 
+(ert-deftest em-prompt-test/after-failure ()
+  "Check that current prompt shows the exit code of the last failed command."
+  (with-temp-eshell
+   (let ((debug-on-error nil))
+     (eshell-insert-command "(zerop \"foo\")"))
+   (let ((current-prompt (field-string (1- (point)))))
+     (should (equal-including-properties
+              current-prompt
+              (propertize
+               (concat (directory-file-name default-directory)
+                       (unless (eshell-exit-success-p)
+                         (format " [%d]" eshell-last-command-status))
+                       (if (= (file-user-uid) 0) " # " " $ "))
+               '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)))))))
+
 (ert-deftest em-prompt-test/field-properties ()
   "Check that field properties are properly set on Eshell output/prompts."
   (with-temp-eshell
@@ -88,6 +107,8 @@ em-prompt-test--with-multiline
 (defun em-prompt-test/next-previous-prompt-with ()
   "Helper for checking forward/backward navigation of old prompts."
   (with-temp-eshell
+   (let ((debug-on-error nil))
+     (eshell-insert-command "(zerop \"foo\")")) ; A failed command.
    (eshell-insert-command "echo one")
    (eshell-insert-command "echo two")
    (eshell-insert-command "echo three")
@@ -99,8 +120,11 @@ em-prompt-test/next-previous-prompt-with
    (end-of-line)
    (eshell-previous-prompt 2)
    (should (equal (eshell-get-old-input) "echo one"))
-   ;; Go forward three prompts.
-   (eshell-next-prompt 3)
+   ;; Go back one prompt.
+   (eshell-previous-prompt 1)
+   (should (equal (eshell-get-old-input) "(zerop \"foo\")"))
+   ;; Go forward four prompts.
+   (eshell-next-prompt 4)
    (should (equal (eshell-get-old-input) "echo fou"))))
 
 (ert-deftest em-prompt-test/next-previous-prompt ()
@@ -115,6 +139,8 @@ em-prompt-test/next-previous-prompt-multiline
 (defun em-prompt-test/forward-backward-matching-input-with ()
   "Helper for checking forward/backward navigation via regexps."
   (with-temp-eshell
+   (let ((debug-on-error nil))
+     (eshell-insert-command "(zerop \"foo\")")) ; A failed command.
    (eshell-insert-command "echo one")
    (eshell-insert-command "printnl something else")
    (eshell-insert-command "echo two")
@@ -127,8 +153,11 @@ em-prompt-test/forward-backward-matching-input-with
    (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)
+   ;; Go back one prompt.
+   (eshell-previous-prompt 1)
+   (should (equal (eshell-get-old-input) "(zerop \"foo\")"))
+   ;; Go forward four prompts.
+   (eshell-forward-matching-input "echo" 4)
    (should (equal (eshell-get-old-input) "echo fou"))))
 
 (ert-deftest em-prompt-test/forward-backward-matching-input ()
-- 
2.42.0