diff options
Diffstat (limited to 'modules/eshell-config.el')
| -rw-r--r-- | modules/eshell-config.el | 136 |
1 files changed, 103 insertions, 33 deletions
diff --git a/modules/eshell-config.el b/modules/eshell-config.el index d3c8ccdfd..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 @@ -153,35 +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 - ;; Two hooks. eshell-before-prompt is the real hook name; use-package appends - ;; "-hook", so writing eshell-before-prompt-hook here registered on a - ;; nonexistent eshell-before-prompt-hook-hook and never ran. The eshell-mode - ;; hook scopes TERM=xterm-256color to eshell-spawned processes only (a global - ;; setenv would leak it to every start-process regardless of terminal). - :hook - ((eshell-before-prompt . (lambda () - (setq xterm-color-preserve-properties t))) - (eshell-mode . (lambda () - (setq-local process-environment - (cons "TERM=xterm-256color" - process-environment))))) - :config - ;; Wire xterm-color into eshell's output pipeline (per its README): install - ;; the filter and drop eshell's own ANSI handler. Without this the escapes are - ;; never interpreted and TERM=xterm-256color only leaks raw codes. - (add-to-list 'eshell-preoutput-filter-functions 'xterm-color-filter) - (setq eshell-output-filter-functions - (remove 'eshell-handle-ansi-color eshell-output-filter-functions))) +;; 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 |
