Subject: bug#65604: [PATCH] Display the exit code if the last command failed in Eshell

From: Davide Masserut <[email-masked]>
Date: Wed, 30 Aug 2023 17:26:27 +0200

Sorry, I forgot to make a variable buffer-local.

>From d418c7a9a88cc2c5aff2e0ff7ad9681c0c0016bb Mon Sep 17 00:00:00 2001
From: Davide Masserut <[email-masked]>
Date: Wed, 30 Aug 2023 16:38:07 +0200
Subject: [PATCH] Display the exit code if the last command failed in Eshell

* etc/NEWS: Announce change.
* lisp/eshell/esh-cmd.el (eshell-exec-lisp):
(eshell-lisp-command): Use new helper function.
* lisp/eshell/esh-io.el:
(eshell-close-handles): Use new helper function.
(eshell-update-last-command-status): Add new helper function.
* test/lisp/eshell/em-io-tests.el
(em-io-test/modeline-after-failure): Add new test.
---
 etc/NEWS                         |  3 +++
 lisp/eshell/esh-cmd.el           |  8 ++++----
 lisp/eshell/esh-io.el            | 17 +++++++++++++++--
 test/lisp/eshell/esh-io-tests.el | 15 +++++++++++++++
 4 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 9a98db8c83a..810172e3b11 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.
 
+---
+*** If the last command failed, its exit code is now displayed in the modeline.
+
 ** Pcomplete
 
 ---
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 80066263396..3672481a66a 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -1407,10 +1407,10 @@ eshell-exec-lisp
      ;; command status to some non-zero value to indicate an error; to
      ;; match GNU/Linux, we use 141, which the numeric value of
      ;; SIGPIPE on GNU/Linux (13) with the high bit (2^7) set.
-     (setq eshell-last-command-status 141)
+     (eshell-update-last-command-status 141)
      nil)
     (error
-     (setq eshell-last-command-status 1)
+     (eshell-update-last-command-status 1)
      (let ((msg (error-message-string err)))
        (if (and (not form-p)
                 (string-match "^Wrong number of arguments" msg)
@@ -1481,8 +1481,8 @@ eshell-lisp-command
   (unless eshell-allow-commands
     (signal 'eshell-commands-forbidden '(lisp)))
   (catch 'eshell-external               ; deferred to an external command
-    (setq eshell-last-command-status 0
-          eshell-last-arguments args)
+    (eshell-update-last-command-status 0)
+    (setq eshell-last-arguments args)
     (let* ((eshell-ensure-newline-p (eshell-interactive-output-p))
            (command-form-p (functionp object))
            (result
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index c07f871dd37..96ce08051b4 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -70,6 +70,7 @@
 
 (require 'esh-arg)
 (require 'esh-util)
+(require 'compile)
 
 (eval-when-compile
   (require 'cl-lib))
@@ -170,7 +171,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
@@ -362,7 +363,7 @@ eshell-close-handles
 RESULT is the quoted value of the last command.  If nil, then use
 the value already set in `eshell-last-command-result'."
   (when exit-code
-    (setq eshell-last-command-status exit-code))
+    (eshell-update-last-command-status exit-code))
   (when result
     (cl-assert (eq (car result) 'quote))
     (setq eshell-last-command-result (cadr result)))
@@ -670,5 +671,17 @@ eshell-output-object
     (dolist (target targets)
       (eshell-output-object-to-target object target))))
 
+(defun eshell-update-last-command-status (exit-code)
+  "Set `eshell-last-command-status' to EXIT-CODE and update `mode-line-process'."
+  (setq mode-line-process
+        (when (> exit-code 0)
+            (list
+             (let ((out-string (format ":[%s]" exit-code))
+                   (msg (format "Last command exited with code %s" exit-code)))
+               (propertize out-string
+                           'help-echo msg
+                           'face 'compilation-mode-line-fail))))
+        eshell-last-command-status exit-code))
+
 (provide 'esh-io)
 ;;; esh-io.el ends here
diff --git a/test/lisp/eshell/esh-io-tests.el b/test/lisp/eshell/esh-io-tests.el
index ce80f3a8f08..c134f262007 100644
--- a/test/lisp/eshell/esh-io-tests.el
+++ b/test/lisp/eshell/esh-io-tests.el
@@ -23,6 +23,7 @@
 (require 'ert-x)
 (require 'esh-mode)
 (require 'eshell)
+(require 'compile)
 
 (require 'eshell-tests-helpers
          (expand-file-name "eshell-tests-helpers"
@@ -370,4 +371,18 @@ esh-io-test/virtual/dev-kill
    (eshell-insert-command "echo three >> /dev/kill")
    (should (equal (car kill-ring) "twothree"))))
 
+(ert-deftest esh-io-test/modeline-after-failure ()
+  "Check that exit code is displayed after a failure."
+  (with-temp-eshell
+   (let ((debug-on-error nil))
+     (eshell-insert-command "(zerop \"foo\")")) ; A failed command.
+   (should (equal-including-properties
+            mode-line-process
+            (list
+             (let ((out-string (format ":[%s]" eshell-last-command-status))
+                   (msg (format "Last command exited with code %s" eshell-last-command-status)))
+               (propertize out-string
+                           'help-echo msg
+                           'face 'compilation-mode-line-fail)))))))
+
 ;;; esh-io-tests.el ends here
-- 
2.42.0