(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")))