summaryrefslogtreecommitdiff
path: root/files/emacs/.emacs.d/eshell.el
diff options
context:
space:
mode:
authorMitch Taylor <mitch@mitchtaylor.xyz>2026-03-22 22:07:22 -0300
committerMitch Taylor <mitch@mitchtaylor.xyz>2026-03-22 22:07:22 -0300
commit04126e9fda050fa753ddc22511e3c3f8dc275f7a (patch)
tree41d9f9276c1e09c1cb86f94133bdf90f041d1ed2 /files/emacs/.emacs.d/eshell.el
initial commit
Diffstat (limited to 'files/emacs/.emacs.d/eshell.el')
-rw-r--r--files/emacs/.emacs.d/eshell.el207
1 files changed, 207 insertions, 0 deletions
diff --git a/files/emacs/.emacs.d/eshell.el b/files/emacs/.emacs.d/eshell.el
new file mode 100644
index 0000000..f48142d
--- /dev/null
+++ b/files/emacs/.emacs.d/eshell.el
@@ -0,0 +1,207 @@
+(use-package eshell
+ :ensure nil
+ :bind
+ (("C-c e" . eshell)
+ ("C-!" . eshell-here))
+ :defer t
+ :config
+ (setq eshell-history-size 100000)
+ (setq eshell-hist-ignoredups t)
+
+
+ ;; MAKE ALL INSTANCES OF ESHELL SHARE/MERGE ITS COMMAND HISTORY
+ ;;
+ (defun emacs-solo/eshell--collect-all-history ()
+ "Return a list of all eshell history entries from all buffers and disk."
+ (let ((history-from-buffers
+ (cl-loop for buf in (buffer-list)
+ when (with-current-buffer buf (derived-mode-p 'eshell-mode))
+ append (with-current-buffer buf
+ (when (boundp 'eshell-history-ring)
+ (ring-elements eshell-history-ring)))))
+ (history-from-file
+ (when (file-exists-p eshell-history-file-name)
+ (with-temp-buffer
+ (insert-file-contents eshell-history-file-name)
+ (split-string (buffer-string) "\n" t)))))
+ (seq-uniq (append history-from-buffers history-from-file))))
+
+ (defun emacs-solo/eshell--save-merged-history ()
+ "Save all eshell buffer histories merged into `eshell-history-file-name`."
+ (let ((all-history (emacs-solo/eshell--collect-all-history)))
+ (with-temp-file eshell-history-file-name
+ (insert (mapconcat #'identity all-history "\n")))))
+
+ (add-hook 'kill-emacs-hook #'emacs-solo/eshell--save-merged-history)
+
+ (add-hook 'eshell-mode-hook
+ (lambda ()
+ (eshell-read-history)))
+
+ ;; MAKES C-c l GIVE AN ICOMPLETE LIKE SEARCH TO HISTORY COMMANDS
+ ;;
+ (defun emacs-solo/eshell-pick-history ()
+ "Show a unified and unique Eshell history from all open sessions + history file.
+Pre-fills the minibuffer with current Eshell input (from prompt to point)."
+ (interactive)
+ (unless (derived-mode-p 'eshell-mode)
+ (user-error "This command must be called from an Eshell buffer"))
+ (let* (;; Safely get current input from prompt to point
+ (bol (save-excursion (eshell-bol) (point)))
+ (eol (point))
+ (current-input (buffer-substring-no-properties bol eol))
+
+ ;; Path to Eshell history file
+ (history-file (expand-file-name eshell-history-file-name
+ eshell-directory-name))
+
+ ;; Read from history file
+ (history-from-file
+ (when (file-exists-p history-file)
+ (with-temp-buffer
+ (insert-file-contents-literally history-file)
+ (split-string (buffer-string) "\n" t))))
+
+ ;; Read from in-memory Eshell buffers
+ (history-from-rings
+ (cl-loop for buf in (buffer-list)
+ when (with-current-buffer buf (derived-mode-p 'eshell-mode))
+ append (with-current-buffer buf
+ (when (bound-and-true-p eshell-history-ring)
+ (ring-elements eshell-history-ring)))))
+
+ ;; Deduplicate and sort
+ (all-history (reverse
+ (seq-uniq
+ (seq-filter (lambda (s) (and s (not (string-empty-p s))))
+ (append history-from-rings history-from-file)))))
+
+ ;; Prompt user with current input as initial suggestion
+ (selection (completing-read "Eshell History: " all-history
+ nil t current-input)))
+
+ (when selection
+ ;; Replace current input with selected history entry
+ (delete-region bol eol)
+ (insert selection))))
+
+
+ ;; GIVES SYNTAX HIGHLIGHTING TO CAT
+ ;;
+ (defun eshell/cat-with-syntax-highlighting (filename)
+ "Like cat(1) but with syntax highlighting.
+ Stole from aweshell"
+ (let ((existing-buffer (get-file-buffer filename))
+ (buffer (find-file-noselect filename)))
+ (eshell-print
+ (with-current-buffer buffer
+ (if (fboundp 'font-lock-ensure)
+ (font-lock-ensure)
+ (with-no-warnings
+ (font-lock-fontify-buffer)))
+ (let ((contents (buffer-string)))
+ (remove-text-properties 0 (length contents) '(read-only nil) contents)
+ contents)))
+ (unless existing-buffer
+ (kill-buffer buffer))
+ nil))
+ (advice-add 'eshell/cat :override #'eshell/cat-with-syntax-highlighting)
+
+
+ ;; LOCAL ESHELL BINDINGS
+ ;;
+ (add-hook 'eshell-mode-hook
+ (lambda ()
+ (local-set-key (kbd "C-c l") #'emacs-solo/eshell-pick-history)
+ (local-set-key (kbd "C-l")
+ (lambda ()
+ (interactive)
+ (eshell/clear 1)))))
+
+ (defun eshell-here ()
+ "Opens up a new shell in the directory associated with the
+current buffer's file. The eshell is renamed to match that
+directory to make multiple eshell windows easier."
+ (interactive)
+ (let* ((parent (if (buffer-file-name)
+ (file-name-directory (buffer-file-name))
+ default-directory))
+ (height (/ (window-total-height) 3))
+ (name (car (last (split-string parent "/" t)))))
+ (split-window-vertically (- height))
+ (other-window 1)
+ (eshell "new")
+ (rename-buffer (concat "*eshell: " name "*"))))
+
+ (defun eshell/x ()
+ (insert "exit")
+ (eshell-send-input)
+ (delete-window))
+
+ (defun fish-path (path max-len)
+ "Return a potentially trimmed-down version of the directory PATH, replacing
+parent directories with their initial characters to try to get the character
+length of PATH (sans directory slashes) down to MAX-LEN."
+ (let* ((components (split-string (abbreviate-file-name path) "/"))
+ (len (+ (1- (length components))
+ (cl-reduce '+ components :key 'length)))
+ (str ""))
+ (while (and (> len max-len)
+ (cdr components))
+ (setq str (concat str
+ (cond ((= 0 (length (car components))) "/")
+ ((= 1 (length (car components)))
+ (concat (car components) "/"))
+ (t
+ (if (string= "."
+ (string (elt (car components) 0)))
+ (concat (substring (car components) 0 2)
+ "/")
+ (string (elt (car components) 0) ?/)))))
+ len (- len (1- (length (car components))))
+ components (cdr components)))
+ (concat str (cl-reduce (lambda (a b) (concat a "/" b)) components))))
+
+ (defun heks-emacs-eshell-prompt ()
+ (defun with-face (str &rest face-plist)
+ (propertize str 'face face-plist))
+ (concat
+ "\n"
+ (with-face user-login-name :inherit 'font-lock-function-name-face)
+ "@"
+ (with-face (system-name) :inherit 'font-lock-function-name-face)
+ ": "
+ (with-face (if (string= (eshell/pwd) (getenv "HOME"))
+ "~" (fish-path (eshell/pwd) 36))
+ :inherit 'font-lock-function-name-face)
+ ": "
+ (with-face
+ (or (ignore-errors (git-prompt-branch-name)) "")
+ :inherit 'font-lock-function-name-face)
+ "\n"
+ (if (= (user-uid) 0)
+ "#"
+ "λ")
+ " "))
+
+ (setopt eshell-prompt-regexp "^[^#λ\n]*[#λ] ")
+ (setopt eshell-prompt-function 'heks-emacs-eshell-prompt)
+ (setopt eshell-highlight-prompt nil)
+
+
+ ;; SET TERM ENV SO MOST PROGRAMS WON'T COMPLAIN
+ ;;
+ (add-hook 'eshell-mode-hook (lambda () (setenv "TERM" "xterm-256color")))
+
+
+ (setq eshell-visual-subcommands
+ '(("podman" "run" "exec" "attach" "top" "logs" "stats" "compose")
+ ("docker" "run" "exec" "attach" "top" "logs" "stats" "compose")
+ ("jj" "resolve" "squash" "split")))
+
+ (setq eshell-visual-commands
+ '("vi" "screen" "top" "htop" "btm" "less" "more" "lynx" "ncftp" "pine" "tin" "trn"
+ "elm" "irssi" "nmtui-connect" "nethack" "vim" "alsamixer" "nvim" "w3m" "psql"
+ "lazygit" "lazydocker" "ncmpcpp" "newsbeuter" "nethack" "mutt" "neomutt" "tmux"
+ "jqp")))
+