branch: elpa/casual
commit c1b766fed048ecb1a5826df6783e3c6cd94e9098
Author: Charles Choi <[email protected]>
Commit: Charles Choi <[email protected]>

    Add Casual Eshell
    
    Adds support for Eshell with the menu `casual-eshell-tmenu`.
---
 README.org                                         |   6 +
 docs/casual.info                                   | 134 ++++++++++++------
 docs/casual.org                                    |  36 ++++-
 docs/eshell.org                                    |  49 +++++++
 docs/images/casual-eshell-process-screenshot.png   | Bin 0 -> 228726 bytes
 docs/images/casual-eshell-screenshot.png           | Bin 0 -> 152307 bytes
 lisp/Makefile                                      |   5 +
 lisp/Makefile-eshell.make                          |  35 +++++
 lisp/casual-eshell-settings.el                     |  74 ++++++++++
 lisp/casual-eshell-utils.el                        | 106 +++++++++++++++
 lisp/casual-eshell.el                              | 151 +++++++++++++++++++++
 tests/Makefile                                     |   5 +
 ...n-test-utils.el => casual-eshell-test-utils.el} |  17 ++-
 tests/casual-help-test-utils.el                    |   2 +-
 tests/casual-man-test-utils.el                     |   2 +-
 tests/test-casual-eshell-settings.el               |  50 +++++++
 tests/test-casual-eshell-utils.el                  |  54 ++++++++
 tests/test-casual-eshell.el                        | 116 ++++++++++++++++
 18 files changed, 790 insertions(+), 52 deletions(-)

diff --git a/README.org b/README.org
index 1a9198c3ae..0abb08e3dd 100644
--- a/README.org
+++ b/README.org
@@ -37,6 +37,7 @@ Editorially, all design decisions for Casual are ultimately 
the opinion of Charl
   - [[#calendar-elisp-library-casual-calendar][Calendar (Elisp library: 
~casual-calendar~)]]
   - [[#dired-elisp-library-casual-dired][Dired (Elisp library: 
~casual-dired~)]]
   - [[#editkit-elisp-library-casual-editkit][EditKit (Elisp library: 
~casual-editkit~)]]
+  - [[#eshell-elisp-library-casual-eshell][Eshell (Elisp library: 
~casual-eshell~)]]
   - [[#help-elisp-library-casual-help][Help (Elisp library: ~casual-help~)]]
   - [[#ibuffer-elisp-library-casual-ibuffer][IBuffer (Elisp library: 
~casual-ibuffer~)]]
   - [[#image-elisp-library-casual-image][Image (Elisp library: 
~casual-image~)]]
@@ -92,6 +93,10 @@ A cornucopia of interfaces for the different editing 
features (e.g. marking, cop
 
   
[[file:docs/editkit.org][file:docs/images/casual-editkit-main-screenshot.png]]
 
+** [[file:docs/eshell.org][Eshell]] (Elisp library: ~casual-eshell~)
+An interface for ~Eshell~, a shell-like command interpreter implemented in 
Emacs Lisp.
+
+  [[file:docs/eshell.org][file:docs/images/casual-eshell-screenshot.png]]
 
 ** [[file:docs/help.org][Help]] (Elisp library: ~casual-help~)
 
@@ -164,6 +169,7 @@ Configuration of a particular Casual user interface is 
performed per mode. Go to
 - [[file:docs/calendar.org::*Install][Calendar]]
 - [[file:docs/dired.org::*Install][Dired]]
 - [[file:docs/editkit.org::*Install][EditKit]]
+- [[file:docs/eshell.org::*Install][Eshell]]
 - [[file:docs/help.org::*Install][Help]]
 - [[file:docs/ibuffer.org::*Install][IBuffer]]
 - [[file:docs/image.org::*Install][Image]]
diff --git a/docs/casual.info b/docs/casual.info
index cf889aa1c0..5c36ed3aa6 100644
--- a/docs/casual.info
+++ b/docs/casual.info
@@ -11,7 +11,7 @@ File: casual.info,  Node: Top,  Next: Motivations,  Up: (dir)
 Casual User Guide
 *****************
 
-Version: 2.6.0
+Version: 2.7.0
 
 Casual is a project to re-imagine the primary user interface for Emacs
 using keyboard-driven menus.
@@ -61,6 +61,7 @@ Casual Modes
 * Calendar::
 * Dired::
 * EditKit::
+* Eshell::
 * Help::
 * IBuffer::
 * Image::
@@ -209,6 +210,7 @@ The following modes are supported by Casual:
 * Calendar::
 * Dired::
 * EditKit::
+* Eshell::
 * Help::
 * IBuffer::
 * Image::
@@ -624,7 +626,7 @@ References
    • *note Dired: (emacs)Dired.
 
 
-File: casual.info,  Node: EditKit,  Next: Help,  Prev: Dired,  Up: Casual Modes
+File: casual.info,  Node: EditKit,  Next: Eshell,  Prev: Dired,  Up: Casual 
Modes
 
 4.6 EditKit
 ===========
@@ -685,9 +687,57 @@ References
    • *note Registers: (emacs)Registers.
 
 
-File: casual.info,  Node: Help,  Next: IBuffer,  Prev: EditKit,  Up: Casual 
Modes
+File: casual.info,  Node: Eshell,  Next: Help,  Prev: EditKit,  Up: Casual 
Modes
 
-4.7 Help
+4.7 Eshell
+==========
+
+Casual Eshell is a user interface for ‘Eshell’, a shell-like command
+interpreter implemented in Emacs Lisp.
+
+Configuration
+=============
+
+In your initialization file, bind the Transient ‘casual-eshell-tmenu’ to
+your key binding of preference.
+
+     (require 'casual-eshell) ; optional
+     (keymap-set eshell-mode-map "C-o" #'casual-eshell-tmenu)
+
+Usage
+=====
+
+Basic Usage
+-----------
+
+Eshell can be invoked via ‘M-x eshell’.  In any Eshell window, pressing
+‘C-o’ (or your binding of preference) will raise the menu
+‘casual-eshell-tmenu’.
+
+The following sections are offered in the menu:
+
+Input
+     Commands supporting input to the current prompt.
+Argument
+     Commands supporting arguments in the current prompt.
+Prompt
+     Navigation of previous prompt commands.
+Output
+     Commands related to display of prompt.  Commands marked with an ‘n’
+     superscript support an optional prefix (‘C-u’) value.
+Misc
+     Miscellaneous commands.
+
+If a process is running, a section for managing signals to send to it is
+provided.
+
+Process
+     Signal commands to send to the process.
+
+
+File: casual.info,  Node: Help,  Next: IBuffer,  Prev: Eshell,  Up: Casual 
Modes
+
+4.8 Help
 ========
 
 Casual Help is a user interface for ‘help-mode’, a major mode for
@@ -755,7 +805,7 @@ Source
 
 File: casual.info,  Node: IBuffer,  Next: Image,  Prev: Help,  Up: Casual Modes
 
-4.8 IBuffer
+4.9 IBuffer
 ===========
 
 Casual IBuffer provides a user interface to Emacs IBuffer, a mode
@@ -830,8 +880,8 @@ References
 
 File: casual.info,  Node: Image,  Next: Info,  Prev: IBuffer,  Up: Casual Modes
 
-4.9 Image
-=========
+4.10 Image
+==========
 
 Casual Image is a user interface for Image Mode.  Its top level library
 is ‘casual-image’.
@@ -890,7 +940,7 @@ References
 
 File: casual.info,  Node: Info,  Next: I-Search,  Prev: Image,  Up: Casual 
Modes
 
-4.10 Info
+4.11 Info
 =========
 
 Casual Info is a user interface for the Emacs Info Reader.  Its top
@@ -953,7 +1003,7 @@ References
 
 File: casual.info,  Node: I-Search,  Next: Make,  Prev: Info,  Up: Casual Modes
 
-4.11 I-Search
+4.12 I-Search
 =============
 
 Casual I-Search is a user interface for Incremental Search.  Its top
@@ -1005,7 +1055,7 @@ References
 
 File: casual.info,  Node: Make,  Next: Man,  Prev: I-Search,  Up: Casual Modes
 
-4.12 Make
+4.13 Make
 =========
 
 Casual Make is a user interface to ‘make-mode’, a mode tailored for
@@ -1084,7 +1134,7 @@ References
 
 File: casual.info,  Node: Man,  Next: RE-Builder,  Prev: Make,  Up: Casual 
Modes
 
-4.13 Man
+4.14 Man
 ========
 
 Casual Man is a user interface for ‘Man-mode’, a Man page reader.
@@ -1171,7 +1221,7 @@ manual pages.
 
 File: casual.info,  Node: RE-Builder,  Next: Timezone,  Prev: Man,  Up: Casual 
Modes
 
-4.14 RE-Builder
+4.15 RE-Builder
 ===============
 
 Casual RE-Builder is a user interface for RE-Builder.  Its top level
@@ -1263,7 +1313,7 @@ References
 
 File: casual.info,  Node: Timezone,  Prev: RE-Builder,  Up: Casual Modes
 
-4.15 Timezone
+4.16 Timezone
 =============
 
 Casual Timezone is a library of commands to work with different time
@@ -1523,6 +1573,9 @@ File: casual.info,  Node: Index,  Next: Variable Index,  
Prev: Acknowledgments,
 * EditKit:                               EditKit.              (line  6)
 * EditKit Configuration:                 EditKit.              (line 15)
 * EditKit Usage:                         EditKit.              (line 25)
+* Eshell:                                Eshell.               (line  6)
+* Eshell Configuration:                  Eshell.               (line 12)
+* Eshell Usage:                          Eshell.               (line 20)
 * Feedback:                              Feedback & Discussion.
                                                                (line  6)
 * Help:                                  Help.                 (line  6)
@@ -1578,33 +1631,34 @@ File: casual.info,  Node: Variable Index,  Prev: Index, 
 Up: Top
 
 Tag Table:
 Node: Top228
-Node: Motivations1953
-Node: Requirements3446
-Node: Transient Conventions3710
-Node: Casual Modes5438
-Node: Agenda6460
-Node: Bookmarks8017
-Node: Calc10292
-Node: Calendar13433
-Node: Dired14781
-Node: EditKit18006
-Node: Help19727
-Node: IBuffer22436
-Node: Image24498
-Node: Info25739
-Node: I-Search27207
-Node: Make28368
-Node: Man30745
-Node: RE-Builder34132
-Node: Timezone37555
-Node: UX Conventions40947
-Node: Customization43648
-Node: Feedback & Discussion44022
-Node: Sponsorship44440
-Node: About44734
-Node: Acknowledgments45011
-Node: Index45393
-Node: Variable Index50171
+Node: Motivations1964
+Node: Requirements3457
+Node: Transient Conventions3721
+Node: Casual Modes5449
+Node: Agenda6482
+Node: Bookmarks8039
+Node: Calc10314
+Node: Calendar13455
+Node: Dired14803
+Node: EditKit18028
+Node: Eshell19751
+Node: Help20966
+Node: IBuffer23674
+Node: Image25736
+Node: Info26979
+Node: I-Search28447
+Node: Make29608
+Node: Man31985
+Node: RE-Builder35372
+Node: Timezone38795
+Node: UX Conventions42187
+Node: Customization44888
+Node: Feedback & Discussion45262
+Node: Sponsorship45680
+Node: About45974
+Node: Acknowledgments46251
+Node: Index46633
+Node: Variable Index51630
 
 End Tag Table
 
diff --git a/docs/casual.org b/docs/casual.org
index 70064e93bb..87d1c745e3 100644
--- a/docs/casual.org
+++ b/docs/casual.org
@@ -6,7 +6,7 @@
 #+OPTIONS: ':t toc:t author:t email:t compact-itemx:t
 #+LANGUAGE: en
 
-#+MACRO: version 2.6.0
+#+MACRO: version 2.7.0
 
 #+TEXINFO_FILENAME: casual.info
 #+TEXINFO_HEADER: @syncodeindex pg cp
@@ -488,6 +488,40 @@ Casual EditKit has support for Register commands by 
providing a sub-menu for it
 #+TEXINFO: @unnumberedsec References
 - [[info:emacs#Registers][Registers]]
 
+** Eshell
+#+CINDEX: Eshell
+
+Casual Eshell is a user interface for ~Eshell~, a shell-like command 
interpreter implemented in Emacs Lisp.
+
+#+TEXINFO: @unnumberedsec Configuration
+#+CINDEX: Eshell Configuration
+
+In your initialization file, bind the Transient ~casual-eshell-tmenu~ to your 
key binding of preference.
+
+#+begin_src elisp :lexical no
+  (require 'casual-eshell) ; optional
+  (keymap-set eshell-mode-map "C-o" #'casual-eshell-tmenu)
+#+end_src
+
+#+TEXINFO: @unnumberedsec Usage
+#+CINDEX: Eshell Usage
+
+#+TEXINFO: @unnumberedsubsec Basic Usage
+
+Eshell can be invoked via ~M-x eshell~. In any Eshell window, pressing ~C-o~ 
(or your binding of preference) will raise the menu ~casual-eshell-tmenu~.
+
+The following sections are offered in the menu:
+
+- Input :: Commands supporting input to the current prompt. 
+- Argument :: Commands supporting arguments in the current prompt.
+- Prompt :: Navigation of previous prompt commands.
+- Output :: Commands related to display of prompt. Commands marked with an ~n~ 
superscript support an optional prefix (~C-u~) value.
+- Misc :: Miscellaneous commands.
+
+If a process is running, a section for managing signals to send to it is 
provided.
+  
+- Process :: Signal commands to send to the process.
+
 ** Help
 #+CINDEX: Help
 
diff --git a/docs/eshell.org b/docs/eshell.org
new file mode 100644
index 0000000000..dcefef9055
--- /dev/null
+++ b/docs/eshell.org
@@ -0,0 +1,49 @@
+[[../README.org][❮ Back to Casual]]
+
+* Casual Eshell
+
+Casual Eshell is a user interface for ~Eshell~, a shell-like command 
interpreter implemented in Emacs Lisp.
+
+[[file:images/casual-eshell-screenshot.png]]
+
+* Install
+
+In your initialization file, bind the Transient ~casual-eshell-tmenu~ to your 
key binding of preference.
+
+#+begin_src elisp :lexical no
+  (require 'casual-eshell) ; optional
+  (keymap-set eshell-mode-map "C-o" #'casual-eshell-tmenu)
+#+end_src
+
+
+* Usage
+
+** Basic Usage
+
+Eshell can be invoked via ~M-x eshell~. In any Eshell window, pressing ~C-o~ 
(or your binding of preference) will raise the menu ~casual-eshell-tmenu~.
+
+The following sections are offered in the menu:
+
+- Input :: Commands supporting input to the current prompt. 
+- Argument :: Commands supporting arguments in the current prompt.
+- Prompt :: Navigation of previous prompt commands.
+- Output :: Commands related to display of prompt. Commands marked with an ~n~ 
superscript support an optional prefix (~C-u~) value.
+- Misc :: Miscellaneous commands.
+
+If a process is running, a section for managing signals to send to it is 
provided.
+  
+- Process :: Signal commands to send to the process.
+
+  [[file:images/casual-eshell-process-screenshot.png]]
+
+  
+*** Unicode Symbol Support
+By enabling “Use Unicode Symbols” from the Settings menu, Casual Eshell will 
use Unicode symbols as appropriate in its menus.
+
+* Sponsorship
+If you enjoy using Casual Eshell, consider making a modest financial 
contribution to help support its development and maintenance.
+
+[[https://www.buymeacoffee.com/kickingvegas][file:images/default-yellow.png]]
+
+* See Also
+[[file:agenda.org][Agenda]], [[file:bookmarks.org][Bookmarks]], 
[[file:calc.org][Calc]], [[file:calendar.org][Calendar]], 
[[file:dired.org][Dired]], [[file:editkit.org][EditKit (numerous editing 
commands)]], [[file:eshell.org][Eshell]], [[file:help.org][Help]], 
[[file:ibuffer.org][IBuffer]], [[file:image.org][Image]], 
[[file:info.org][Info]], [[file:isearch.org][I-Search]], 
[[file:make-mode.org][Make]], [[file:man.org][Man]], 
[[file:re-builder.org][RE-Builder]], [[file:timezone.org][Timezone]]
diff --git a/docs/images/casual-eshell-process-screenshot.png 
b/docs/images/casual-eshell-process-screenshot.png
new file mode 100644
index 0000000000..40dcbb0751
Binary files /dev/null and b/docs/images/casual-eshell-process-screenshot.png 
differ
diff --git a/docs/images/casual-eshell-screenshot.png 
b/docs/images/casual-eshell-screenshot.png
new file mode 100644
index 0000000000..9899f2e4d3
Binary files /dev/null and b/docs/images/casual-eshell-screenshot.png differ
diff --git a/lisp/Makefile b/lisp/Makefile
index 67aee24056..5bd00d6df4 100644
--- a/lisp/Makefile
+++ b/lisp/Makefile
@@ -25,6 +25,7 @@ calc-tests                                    \
 calendar-tests                                 \
 dired-tests                                    \
 editkit-tests                                  \
+eshell-tests                                   \
 help-tests                                     \
 ibuffer-tests                                  \
 info-tests                                     \
@@ -62,6 +63,10 @@ dired-tests:
 editkit-tests:
        $(MAKE) -C $(SRC_DIR) -f Makefile-editkit.make tests
 
+.PHONY: eshell-tests
+eshell-tests:
+       $(MAKE) -C $(SRC_DIR) -f Makefile-eshell.make tests
+
 .PHONY: help-tests
 help-tests:
        $(MAKE) -C $(SRC_DIR) -f Makefile-help.make tests
diff --git a/lisp/Makefile-eshell.make b/lisp/Makefile-eshell.make
new file mode 100644
index 0000000000..349f653096
--- /dev/null
+++ b/lisp/Makefile-eshell.make
@@ -0,0 +1,35 @@
+##
+# Copyright (C) 2025 Charles Y. Choi
+#
+# This program 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.
+
+# This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+include Makefile--defines.make
+
+PACKAGE_NAME=casual-eshell
+ELISP_INCLUDES=casual-eshell-utils.el  \
+casual-eshell-settings.el
+ELISP_PACKAGES=
+ELISP_TEST_INCLUDES=casual-eshell-test-utils.el
+PACKAGE_PATHS=                                 \
+-L $(EMACS_ELPA_DIR)/compat-current            \
+-L $(EMACS_ELPA_DIR)/seq-current               \
+-L $(EMACS_ELPA_DIR)/transient-current         \
+-L $(EMACS_ELPA_DIR)/magit-current             \
+-L $(EMACS_ELPA_DIR)/magit-section-current     \
+-L $(EMACS_ELPA_DIR)/dash-current              \
+-L $(EMACS_ELPA_DIR)/with-editor-current       \
+-L $(EMACS_ELPA_DIR)/llama-current             \
+-L $(CASUAL_LIB_LISP_DIR)
+
+include Makefile--rules.make
diff --git a/lisp/casual-eshell-settings.el b/lisp/casual-eshell-settings.el
new file mode 100644
index 0000000000..7654e88fd8
--- /dev/null
+++ b/lisp/casual-eshell-settings.el
@@ -0,0 +1,74 @@
+;;; casual-eshell-settings.el --- Casual Eshell Settings -*- lexical-binding: 
t; -*-
+
+;; Copyright (C) 2025 Charles Y. Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program 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.
+
+;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+(require 'casual-lib)
+
+(transient-define-prefix casual-eshell-settings-tmenu ()
+  "Casual Eshell settings menu."
+  ["Casual Eshell: Settings"
+   [("G" "Eshell Group" casual-eshell--customize-group)
+    (casual-lib-customize-unicode)
+    (casual-lib-customize-hide-navigation)]]
+
+  [:class transient-row
+   (casual-lib-quit-one)
+   ("a" "About" casual-eshell-about :transient nil)
+   (casual-lib-quit-all)])
+
+(defun casual-eshell--customize-group ()
+  "Customize Eshell group."
+  (interactive)
+  (customize-group "eshell"))
+
+
+(defun casual-eshell-about-eshell ()
+  "Casual Eshell is a Transient menu for Eshell.
+
+Learn more about using Casual Eshell at our discussion group on GitHub.
+Any questions or comments about it should be made there.
+URL `https://github.com/kickingvegas/casual/discussions'
+
+If you find a bug or have an enhancement request, please file an issue.
+Our best effort will be made to answer it.
+URL `https://github.com/kickingvegas/casual/issues'
+
+If you enjoy using Casual Eshell, consider making a modest financial
+contribution to help support its development and maintenance.
+URL `https://www.buymeacoffee.com/kickingvegas'
+
+Casual Eshell was conceived and crafted by Charles Choi in San Francisco,
+California.
+
+Thank you for using Casual Eshell.
+
+Always choose love."
+  (ignore))
+
+(defun casual-eshell-about ()
+  "About information for Casual Eshell."
+  (interactive)
+  (describe-function #'casual-eshell-about-eshell))
+
+(provide 'casual-eshell-settings)
+;;; casual-eshell-settings.el ends here
diff --git a/lisp/casual-eshell-utils.el b/lisp/casual-eshell-utils.el
new file mode 100644
index 0000000000..2ec9503ab7
--- /dev/null
+++ b/lisp/casual-eshell-utils.el
@@ -0,0 +1,106 @@
+;;; casual-eshell-utils.el --- Casual Eshell Utils -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025 Charles Y. Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program 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.
+
+;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'eshell)
+(require 'tramp)
+(require 'casual-lib)
+
+(defconst casual-eshell-unicode-db
+  '((:previous . '("↑" "Previous"))
+    (:next . '("↓" "Next"))
+    (:forward . '("→" "Forward"))
+    (:backward . '("←" "Backward"))
+    (:repeat . '("⥅" "Repeat"))
+    (:clear . '("⌫" "Clear")))
+
+  "Unicode symbol DB to use for Eshell Transient menus.")
+
+(defun casual-eshell-unicode-get (key)
+  "Lookup Unicode symbol for KEY in DB.
+
+- KEY symbol used to lookup Unicode symbol in DB.
+
+If the value of customizable variable `casual-lib-use-unicode'
+is non-nil, then the Unicode symbol is returned, otherwise a
+plain ASCII-range string."
+  (casual-lib-unicode-db-get key casual-eshell-unicode-db))
+
+(defun casual-eshell-info ()
+  "Open Info for Eshell."
+  (interactive) (info "(eshell) Top"))
+
+(defun casual-eshell-info-builtins ()
+  "Open Info for Eshell built-in commands."
+  (interactive) (info "(eshell) List of Built-ins"))
+
+(defun casual-eshell-info-aliases ()
+  "Open Info for Eshell aliases."
+  (interactive) (info "(eshell) Aliases"))
+
+(defun casual-eshell-info-remote-access ()
+  "Open Info for Eshell remote access."
+  (interactive) (info "(eshell) Remote Access"))
+
+(defun casual-eshell-info-control-flow ()
+  "Open Info for Eshell control flow."
+  (interactive) (info "(eshell) Control Flow"))
+
+(defun casual-eshell-info-expansion ()
+  "Open Info for Eshell expansion."
+  (interactive) (info "(eshell) Expansion"))
+
+(defun casual-eshell-info-dollars-expansion ()
+  "Open Info for Eshell $ expansion."
+  (interactive) (info "(eshell) Dollars Expansion"))
+
+(defun casual-eshell-info-redirection ()
+  "Open Info for Eshell redirection."
+  (interactive) (info "(eshell) Redirection"))
+
+(defun casual-eshell-info-pipelines ()
+  "Open Info for Eshell pipelines."
+  (interactive) (info "(eshell) Pipelines"))
+
+(defun casual-eshell-edit-aliases ()
+  "Edit file in `eshell-aliases-file'."
+  (interactive)
+  (find-file eshell-aliases-file))
+
+(defun casual-eshell-tilde-path (path)
+  "Parse PATH for display."
+  (let ((test-ssh-path (string-search "/ssh:" path)))
+    (if (and test-ssh-path (= test-ssh-path 0))
+        (let* ((path-obj (tramp-dissect-file-name path))
+               (host (tramp-file-name-host path-obj))
+               (localname (tramp-file-name-localname path-obj)))
+          (format "(%s) %s" host localname))
+
+      (if (string= path (getenv "HOME"))
+          "~"
+        (replace-regexp-in-string
+         (concat "^" (getenv "HOME")) "~" path)))))
+
+(provide 'casual-eshell-utils)
+;;; casual-eshell-utils.el ends here
diff --git a/lisp/casual-eshell.el b/lisp/casual-eshell.el
new file mode 100644
index 0000000000..16702008af
--- /dev/null
+++ b/lisp/casual-eshell.el
@@ -0,0 +1,151 @@
+;;; casual-eshell.el --- Transient UI for Eshell -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025  Charles Y. Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program 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.
+
+;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library provides a Transient-based user interface for `eshell-mode'.
+
+;; INSTALLATION
+
+;; In your initialization file, bind the Transient `casual-eshell-tmenu' to 
your
+;; key binding of preference.
+
+;; (require 'casual-eshell) ; optional if using autoloaded menu
+;; (keymap-set eshell-mode-map "C-o" #'casual-eshell-tmenu)
+
+;;; Code:
+
+(require 'casual-eshell-settings)
+(require 'casual-eshell-utils)
+(require 'casual-editkit)
+(require 'magit-status)
+(require 'esh-mode)
+(require 'esh-arg)
+(require 'em-hist)
+(require 'em-prompt)
+
+;;;###autoload (autoload 'casual-eshell-tmenu "casual-eshell" nil t)
+(transient-define-prefix casual-eshell-tmenu ()
+  "Transient menu for Eshell."
+  :refresh-suffixes t
+  ["Casual Eshell"
+   :description (lambda ()
+                  (format
+                   "Casual Eshell: %s"
+                   (casual-eshell-tilde-path default-directory)))
+   ["Input"
+    :pad-keys t
+    :if-not buffer-narrowed-p
+    ("B" "#<buffer >…" eshell-insert-buffer-name)
+    ("k" "Clear" eshell-kill-input
+    :description (lambda () (casual-eshell-unicode-get :clear)))
+    ;; ("C-p" "Previous" eshell-previous-matching-input-from-input
+    ;;  :description (lambda () (casual-eshell-unicode-get :previous))
+    ;;  :transient t)
+
+    ;; ("C-n" "Next" eshell-next-matching-input-from-input
+    ;;  :description (lambda () (casual-eshell-unicode-get :next)
+    ;;  :transient t)
+    ("h" "History" eshell-list-history)]
+
+   ["Argument"
+    :if-not buffer-narrowed-p
+    ("b" "Backward" eshell-backward-argument
+     :description (lambda () (casual-eshell-unicode-get :backward))
+     :transient t)
+    ("f" "Forward" eshell-forward-argument
+     :description (lambda () (casual-eshell-unicode-get :forward))
+     :transient t)
+    ("y" "Repeat" eshell-repeat-argument
+     :description (lambda () (casual-eshell-unicode-get :repeat))
+     :transient t)]
+
+   ["Prompt"
+    :if-not buffer-narrowed-p
+    ("p" "Previous" eshell-previous-prompt
+     :description (lambda () (casual-eshell-unicode-get :previous))
+     :transient t)
+    ("n" "Next" eshell-next-prompt
+     :description (lambda () (casual-eshell-unicode-get :next))
+     :transient t)
+    ("RET" "Clone" eshell-copy-old-input)]
+
+   ["Output"
+    :if-not buffer-narrowed-p
+    ("s" "Show" eshell-show-output)
+    ("." "Show Max" eshell-show-maximum-output)
+    ("m" "Mark" eshell-mark-output
+     :description (lambda () (if prefix-arg "Narrow" "Markⁿ")))
+    ("D" "Delete" eshell-delete-output
+     :description (lambda () (if prefix-arg "Kill" "Deleteⁿ")))]
+
+   ["Output"
+    :if buffer-narrowed-p
+    ("w" "Widen" (lambda ()
+                   (interactive)
+                   (widen)
+                   (eshell-show-maximum-output)))]
+
+   ["Misc"
+    ("d" "Dired" dired-jump-other-window)
+    ("a" "Edit Aliases" casual-eshell-edit-aliases)
+    ("J" "Jump to Bookmark…" bookmark-jump)
+    ("g" "Magit" magit-status :if casual-editkit-version-controlled-p)]]
+
+  ["Process"
+   :if (lambda () (car eshell-process-list))
+   :class transient-row
+   ("Pi" "Interrupt" eshell-interrupt-process)
+   ("Pk" "Kill" eshell-kill-process)
+   ("Pq" "Quit" eshell-quit-process)]
+
+  [:class transient-row
+   (casual-lib-quit-one)
+   ("i" "ⓘ›" casual-eshell-info-tmenu)
+   ("," "Settings›" casual-eshell-settings-tmenu)
+   (casual-lib-quit-all)])
+
+(transient-define-prefix casual-eshell-info-tmenu ()
+  "Menu for Eshell Info."
+
+  ["Casual Eshell ⓘ"
+   ["Info"
+    ("i" "Info" casual-eshell-info)]
+
+   ["Commands"
+    ("b" "Built-in Commands" casual-eshell-info-builtins)
+    ("a" "Aliases" casual-eshell-info-aliases)
+    ("r" "Remote Access" casual-eshell-info-remote-access)
+    ("c" "Control Flow" casual-eshell-info-control-flow)]
+
+   ["Expansion"
+    ("e" "Expansion" casual-eshell-info-expansion)
+    ("d" "$" casual-eshell-info-dollars-expansion)]
+
+   ["I/O"
+    ("R" "Redirection" casual-eshell-info-redirection)
+    ("p" "Pipelines" casual-eshell-info-pipelines)]]
+
+  [:class transient-row
+   (casual-lib-quit-one)
+   (casual-lib-quit-all)])
+
+(provide 'casual-eshell)
+;;; casual-eshell.el ends here
diff --git a/tests/Makefile b/tests/Makefile
index 94e3b66a4e..ce30421d6e 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -24,6 +24,7 @@ calc-tests                                    \
 calendar-tests                                 \
 dired-tests                                    \
 editkit-tests                                  \
+eshell-tests                                   \
 help-tests                                     \
 ibuffer-tests                                  \
 info-tests                                     \
@@ -42,6 +43,7 @@ calc-tests                                    \
 calendar-tests                                 \
 dired-tests                                    \
 editkit-tests                                  \
+eshell-tests                                   \
 help-tests                                     \
 ibuffer-tests                                  \
 info-tests                                     \
@@ -72,6 +74,9 @@ dired-tests:
 editkit-tests:
        $(MAKE) -C $(SRC_DIR) $@
 
+eshell-tests:
+       $(MAKE) -C $(SRC_DIR) $@
+
 help-tests:
        $(MAKE) -C $(SRC_DIR) $@
 
diff --git a/tests/casual-man-test-utils.el b/tests/casual-eshell-test-utils.el
similarity index 71%
copy from tests/casual-man-test-utils.el
copy to tests/casual-eshell-test-utils.el
index 5f5ce5192a..7bc96fc7a6 100644
--- a/tests/casual-man-test-utils.el
+++ b/tests/casual-eshell-test-utils.el
@@ -1,6 +1,6 @@
-;;; casual-man-test-utils.el --- Casual Test Utils       -*- lexical-binding: 
t; -*-
+;;; casual-eshell-test-utils.el --- Casual Test Utils       -*- 
lexical-binding: t; -*-
 
-;; Copyright (C) 2024-2025  Charles Y. Choi
+;; Copyright (C) 2025  Charles Y. Choi
 
 ;; Author: Charles Choi <[email protected]>
 ;; Keywords: tools
@@ -27,14 +27,13 @@
 (require 'casual-lib)
 (require 'kmacro)
 
-(defun casualt-man-setup ()
-  "Casual man setup."
-  (setopt Man-prefer-synchronous-call t)
-  (man "tar"))
+(defun casualt-eshell-setup ()
+  "Casual Eshell setup."
+    )
 
-(defun casualt-man-breakdown ()
+(defun casualt-eshell-breakdown ()
   "Casual man breakdown."
   )
 
-(provide 'casual-man-test-utils)
-;;; casual-man-test-utils.el ends here
+(provide 'casual-eshell-test-utils)
+;;; casual-eshell-test-utils.el ends here
diff --git a/tests/casual-help-test-utils.el b/tests/casual-help-test-utils.el
index 40e5d36d8e..6d2a482598 100644
--- a/tests/casual-help-test-utils.el
+++ b/tests/casual-help-test-utils.el
@@ -1,6 +1,6 @@
 ;;; casual-help-test-utils.el --- Casual Test Utils       -*- lexical-binding: 
t; -*-
 
-;; Copyright (C) 2024-2025  Charles Y. Choi
+;; Copyright (C) 2025  Charles Y. Choi
 
 ;; Author: Charles Choi <[email protected]>
 ;; Keywords: tools
diff --git a/tests/casual-man-test-utils.el b/tests/casual-man-test-utils.el
index 5f5ce5192a..2ec1ad75cb 100644
--- a/tests/casual-man-test-utils.el
+++ b/tests/casual-man-test-utils.el
@@ -1,6 +1,6 @@
 ;;; casual-man-test-utils.el --- Casual Test Utils       -*- lexical-binding: 
t; -*-
 
-;; Copyright (C) 2024-2025  Charles Y. Choi
+;; Copyright (C) 2025  Charles Y. Choi
 
 ;; Author: Charles Choi <[email protected]>
 ;; Keywords: tools
diff --git a/tests/test-casual-eshell-settings.el 
b/tests/test-casual-eshell-settings.el
new file mode 100644
index 0000000000..267873ee41
--- /dev/null
+++ b/tests/test-casual-eshell-settings.el
@@ -0,0 +1,50 @@
+;;; test-casual-eshell-settings.el --- Casual Make Settings Tests  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2025  Charles Y. Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program 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.
+
+;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ert)
+(require 'casual-eshell-test-utils)
+(require 'casual-eshell-settings)
+
+(ert-deftest test-casual-eshell-settings-tmenu ()
+  (let ()
+    (cl-letf ((casualt-mock #'casual-eshell--customize-group)
+              (casualt-mock #'casual-eshell-about))
+
+      (let ((test-vectors
+             '((:binding "G" :command casual-eshell--customize-group)
+               (:binding "u" :command 
casual-lib-customize-casual-lib-use-unicode)
+               (:binding "n" :command 
casual-lib-customize-casual-lib-hide-navigation)
+               (:binding "a" :command casual-eshell-about))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-eshell-settings-tmenu
+                                        '(lambda () (random 5000)))))))
+
+(ert-deftest test-casual-eshell-about ()
+  (should (stringp (casual-eshell-about))))
+
+(provide 'test-casual-eshell-settings)
+;;; test-casual-eshell-setttings.el ends here
diff --git a/tests/test-casual-eshell-utils.el 
b/tests/test-casual-eshell-utils.el
new file mode 100644
index 0000000000..f54d492bdb
--- /dev/null
+++ b/tests/test-casual-eshell-utils.el
@@ -0,0 +1,54 @@
+;;; test-casual-eshell-utils.el --- Casual Make Utils Tests  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2025  Charles Y. Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program 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.
+
+;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'ert)
+(require 'casual-eshell-test-utils)
+(require 'casual-eshell-utils)
+
+(ert-deftest test-casual-eshell-unicode-get ()
+  (let ((casual-lib-use-unicode nil))
+    (should (string-equal (casual-eshell-unicode-get :previous) "Previous"))
+    (should (string-equal (casual-eshell-unicode-get :next) "Next"))
+    (should (string-equal (casual-eshell-unicode-get :forward) "Forward"))
+    (should (string-equal (casual-eshell-unicode-get :backward) "Backward"))
+    (should (string-equal (casual-eshell-unicode-get :repeat) "Repeat"))
+    (should (string-equal (casual-eshell-unicode-get :clear) "Clear"))
+    )
+
+  (let ((casual-lib-use-unicode t))
+    (should (string-equal (casual-eshell-unicode-get :previous) "↑"))
+    (should (string-equal (casual-eshell-unicode-get :next) "↓"))
+    (should (string-equal (casual-eshell-unicode-get :forward) "→"))
+    (should (string-equal (casual-eshell-unicode-get :backward) "←"))
+    (should (string-equal (casual-eshell-unicode-get :repeat) "⥅"))
+    (should (string-equal (casual-eshell-unicode-get :clear) "⌫"))
+    ))
+
+
+
+
+
+(provide 'test-casual-eshell-utils)
+;;; test-casual-eshell-utils.el ends here
diff --git a/tests/test-casual-eshell.el b/tests/test-casual-eshell.el
new file mode 100644
index 0000000000..2b1cf146d9
--- /dev/null
+++ b/tests/test-casual-eshell.el
@@ -0,0 +1,116 @@
+;;; test-casual-eshell.el --- Casual Make Tests -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025  Charles Y. Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program 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.
+
+;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ert)
+(require 'casual-eshell)
+(require 'casual-lib-test-utils)
+(require 'casual-eshell-test-utils)
+
+(ert-deftest test-casual-eshell-tmenu ()
+  (let ()
+    (casualt-eshell-setup)
+
+    (cl-letf ((casualt-mock #'eshell-insert-buffer-name)
+              (casualt-mock #'eshell-kill-input)
+              (casualt-mock #'eshell-list-history)
+              (casualt-mock #'eshell-backward-argument)
+              (casualt-mock #'eshell-forward-argument)
+              (casualt-mock #'eshell-repeat-argument)
+              (casualt-mock #'eshell-previous-prompt)
+              (casualt-mock #'eshell-next-prompt)
+              (casualt-mock #'eshell-copy-old-input)
+              (casualt-mock #'eshell-show-output)
+              (casualt-mock #'eshell-show-maximum-output)
+              (casualt-mock #'eshell-mark-output)
+              (casualt-mock #'eshell-delete-output)
+              ;;(casualt-mock #'dired)
+              (casualt-mock #'casual-eshell-edit-aliases)
+
+              (casualt-mock #'casual-eshell-info-tmenu)
+              (casualt-mock #'casual-eshell-settings-tmenu)
+              ;;(casualt-mock #'bookmark-jump)
+              ;;(casualt-mock #'magit-status)
+              )
+
+      (let ((test-vectors
+             '((:binding "B" :command eshell-insert-buffer-name)
+               (:binding "k" :command eshell-kill-input)
+               (:binding "h" :command eshell-list-history)
+               (:binding "b" :command eshell-backward-argument)
+               (:binding "f" :command eshell-forward-argument)
+               (:binding "y" :command eshell-repeat-argument)
+               (:binding "p" :command eshell-previous-prompt)
+               (:binding "n" :command eshell-next-prompt)
+               (:binding "RET" :command eshell-copy-old-input)
+               (:binding "s" :command eshell-show-output)
+               (:binding "." :command eshell-show-maximum-output)
+               (:binding "m" :command eshell-mark-output)
+               (:binding "D" :command eshell-delete-output)
+               ;; (:binding "d" :command dired)
+               (:binding "a" :command casual-eshell-edit-aliases)
+               (:binding "i" :command casual-eshell-info-tmenu)
+               (:binding "," :command casual-eshell-settings-tmenu))
+             ))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-eshell-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-eshell-breakdown)))
+
+
+(ert-deftest test-casual-eshell-info-tmenu ()
+  (let ()
+    (casualt-eshell-setup)
+    (cl-letf ((casualt-mock #'casual-eshell-info)
+              (casualt-mock #'casual-eshell-info-builtins)
+              (casualt-mock #'casual-eshell-info-aliases)
+              (casualt-mock #'casual-eshell-info-remote-access)
+              (casualt-mock #'casual-eshell-info-control-flow)
+              (casualt-mock #'casual-eshell-info-expansion)
+              (casualt-mock #'casual-eshell-info-dollars-expansion)
+              (casualt-mock #'casual-eshell-info-redirection)
+              (casualt-mock #'casual-eshell-info-pipelines))
+
+      (let ((test-vectors
+             '(
+               (:binding "i" :command casual-eshell-info)
+               (:binding "b" :command casual-eshell-info-builtins)
+               (:binding "a" :command casual-eshell-info-aliases)
+               (:binding "r" :command casual-eshell-info-remote-access)
+               (:binding "c" :command casual-eshell-info-control-flow)
+               (:binding "e" :command casual-eshell-info-expansion)
+               (:binding "d" :command casual-eshell-info-dollars-expansion)
+               (:binding "R" :command casual-eshell-info-redirection)
+               (:binding "p" :command casual-eshell-info-pipelines)
+               )))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-eshell-info-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-eshell-breakdown)))
+
+(provide 'test-casual-eshell)
+;;; test-casual-eshell.el ends here


Reply via email to