aboutsummaryrefslogtreecommitdiff
path: root/modules/eshell-config.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/eshell-config.el')
-rw-r--r--modules/eshell-config.el134
1 files changed, 105 insertions, 29 deletions
diff --git a/modules/eshell-config.el b/modules/eshell-config.el
index 0439a4673..7379795d2 100644
--- a/modules/eshell-config.el
+++ b/modules/eshell-config.el
@@ -26,6 +26,35 @@
(require 'system-utils)
+;; Eshell is loaded lazily (:commands eshell), so its vars and functions are
+;; not defined when this file is byte-compiled standalone. Declare them to
+;; silence compile-time free-variable / undefined-function warnings.
+(defvar eshell-banner-message)
+(defvar eshell-scroll-to-bottom-on-input)
+(defvar eshell-error-if-no-glob)
+(defvar eshell-hist-ignoredups)
+(defvar eshell-save-history-on-exit)
+(defvar eshell-prefer-lisp-functions)
+(defvar eshell-destroy-buffer-when-process-dies)
+(defvar eshell-prompt-function)
+(defvar eshell-cmpl-cycle-completions)
+(defvar eshell-modules-list)
+(defvar eshell-hist-mode-map)
+(defvar eshell-visual-commands)
+(defvar eshell-visual-subcommands)
+(defvar eshell-visual-options)
+(defvar eshell-history-ring)
+(defvar eshell-preoutput-filter-functions)
+(defvar eshell-output-filter-functions)
+
+(declare-function ring-elements "ring")
+(declare-function eshell-send-input "esh-mode")
+(declare-function eshell/pwd "em-dirs")
+(declare-function eshell/alias "em-alias")
+(declare-function eshell/cd "em-dirs")
+(declare-function eshell-stringify "esh-util")
+(declare-function eat-eshell-mode "eat")
+
(defgroup cj/eshell nil
"Personal Eshell configuration."
:group 'eshell)
@@ -57,6 +86,59 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(dolist (pair (cj/--eshell-ssh-alias-commands hosts))
(eshell/alias (car pair) (cdr pair))))
+;; ---------------------------- prompt segments --------------------------------
+
+(defun cj/--eshell-git-branch ()
+ "Return the current git branch for `default-directory', or nil.
+Reads .git/HEAD directly so it adds no subprocess per prompt, and skips remote
+directories so a TRAMP prompt stays fast."
+ (unless (file-remote-p default-directory)
+ (when-let* ((root (locate-dominating-file default-directory ".git"))
+ (head (expand-file-name ".git/HEAD" root)))
+ (when (file-readable-p head)
+ (with-temp-buffer
+ (insert-file-contents head)
+ (when (looking-at "ref: refs/heads/\\(.*\\)")
+ (string-trim (match-string 1))))))))
+
+(defun cj/--eshell-prompt-status-segment ()
+ "Return the eshell prompt's exit-status segment, or an empty string.
+Shows the last command's exit code in brackets when it was non-zero, mirroring
+the zsh prompt's failure indicator."
+ (let ((status (bound-and-true-p eshell-last-command-status)))
+ (if (or (null status) (zerop status))
+ ""
+ (format " [%d]" status))))
+
+;; ------------------------------- zoxide --------------------------------------
+;; Share the same frecency database as the zsh shell by calling the zoxide
+;; binary: `z' jumps to a remembered directory, and every eshell directory
+;; change feeds `zoxide add' so eshell visits accrue in the same database.
+
+(defun eshell/z (&rest args)
+ "Jump to a directory via zoxide, sharing the zsh zoxide database.
+With no ARGS, cd home. Otherwise query zoxide for the best match and cd there."
+ (if (null args)
+ (eshell/cd)
+ (let ((dir (string-trim
+ (shell-command-to-string
+ (concat "zoxide query -- "
+ (mapconcat #'shell-quote-argument
+ (mapcar #'eshell-stringify args) " "))))))
+ (if (and (not (string-empty-p dir)) (file-directory-p dir))
+ (eshell/cd dir)
+ (error "zoxide: no match for %s"
+ (string-join (mapcar #'eshell-stringify args) " "))))))
+
+(defun cj/--eshell-zoxide-add ()
+ "Record `default-directory' in the zoxide database (skips remote dirs)."
+ (when (and (not (file-remote-p default-directory))
+ (executable-find "zoxide"))
+ (call-process "zoxide" nil 0 nil "add" "--"
+ (expand-file-name default-directory))))
+
+(add-hook 'eshell-directory-change-hook #'cj/--eshell-zoxide-add)
+
(use-package eshell
:ensure nil ;; built-in
:commands (eshell)
@@ -75,15 +157,18 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(setq eshell-prompt-function
(lambda ()
(concat
- (propertize (format-time-string "[%d-%m-%y %T]") 'face '(:foreground "gray"))
+ (propertize (format-time-string "[%d-%m-%y %T]") 'face 'default)
" "
- (propertize (user-login-name) 'face '(:foreground "gray"))
+ (propertize (user-login-name) 'face 'default)
" "
- (propertize (system-name) 'face '(:foreground "gray"))
+ (propertize (system-name) 'face 'default)
":"
- (propertize (abbreviate-file-name (eshell/pwd)) 'face '(:foreground "gray"))
+ (propertize (abbreviate-file-name (eshell/pwd)) 'face 'default)
+ (let ((branch (cj/--eshell-git-branch)))
+ (if branch (propertize (concat " (" branch ")") 'face 'default) ""))
+ (propertize (cj/--eshell-prompt-status-segment) 'face 'default)
"\n"
- (propertize "%" 'face '(:foreground "white"))
+ (propertize "%" 'face 'default)
" ")))
(add-hook
@@ -101,7 +186,8 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(add-hook 'eshell-mode-hook
(lambda ()
- (add-to-list 'eshell-visual-commands '("lf" "ranger" "tail" "htop" "gotop" "mc" "ncdu" "top"))
+ (dolist (cmd '("lf" "ranger" "tail" "htop" "gotop" "mc" "ncdu" "top"))
+ (add-to-list 'eshell-visual-commands cmd))
(add-to-list 'eshell-visual-subcommands '("git" "log" "diff" "show"))
(add-to-list 'eshell-visual-options '("git" "--help" "--paginate"))
@@ -152,30 +238,20 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(delete-window)))
(advice-add 'eshell-life-is-too-much :after 'cj/eshell-delete-window-on-exit)
-(use-package eshell-toggle
- :custom
- (eshell-toggle-size-fraction 2)
- (eshell-toggle-run-command nil)
- (eshell-toggle-init-function #'eshell-toggle-init-eshell)
- :bind
- ("C-<f12>" . eshell-toggle))
+;; Run eshell's external commands through EAT (a real terminal): visual commands
+;; (vim, htop, less) render properly and ANSI output is faithful, while eshell
+;; stays the shell -- elisp functions as commands + TRAMP transparency. EAT
+;; handles color itself, so it supersedes xterm-color for eshell; the
+;; xterm-color block below stays for now and steps aside if colors double up.
+(with-eval-after-load 'esh-mode
+ (require 'eat)
+ (eat-eshell-mode 1))
-(use-package xterm-color
- :after eshell
- :hook
- (eshell-before-prompt-hook . (lambda ()
- (setq xterm-color-preserve-properties t)))
- ;; Scope `TERM=xterm-256color' to eshell-spawned processes only by
- ;; binding the env var on the eshell mode hook. The previous global
- ;; `setenv' at config-time changed `process-environment' for the
- ;; whole Emacs process, so every subsequent `start-process' inherited
- ;; `xterm-256color' regardless of whether the receiver was a terminal
- ;; that could actually interpret the escapes.
- :hook
- (eshell-mode . (lambda ()
- (setq-local process-environment
- (cons "TERM=xterm-256color"
- process-environment)))))
+;; eshell-toggle and xterm-color are retired. F12 opens eshell now (the
+;; dock-and-remember toggle in eat-config.el), and eat-eshell-mode renders
+;; eshell's output through EAT, which handles ANSI color natively -- so
+;; xterm-color's filter and its TERM=xterm-256color override are redundant and
+;; would fight EAT's own TERM=eat-truecolor.
(use-package eshell-syntax-highlighting
:after esh-mode