;;; Eshell ;;; Eshell gets initialized differently. When eshell.el first gets loaded, only ;;; the core is defined and `eshell-load-hook' is called. For every Eshell ;;; session, `eshell-mode' is run: it resets `eshell-mode-map', it loads ;;; modules, runs their hooks and concludes with `eshell-first-time-mode-hook' ;;; (for the first session only) and `eshell-mode-hook'. (setq eshell-directory-name (concat emacs-cache-folder "eshell")) ;;; Use native 'sudo', system sudo asks for password every time. (require 'em-tramp) (with-eval-after-load "esh-module" ; Need a file name because `provide' is before the definition of `eshell-modules-list. ;; Don't print the banner. (delq 'eshell-banner eshell-modules-list) (push 'eshell-tramp eshell-modules-list)) (setq eshell-ls-use-colors t ;; ffap-shell-prompt-regexp changes the behaviour of `helm-find-files' when ;; point is on prompt. I find this disturbing. ffap-shell-prompt-regexp nil eshell-destroy-buffer-when-process-dies t) ;; TODO: Hour is printed twice. We don't need to set this? ;; (setq eshell-ls-date-format (replace-regexp-in-string "^\\+*" "" (getenv "TIME_STYLE"))) ;;; Leave `eshell-highlight-prompt' to t as it sets the read-only property. (setq eshell-prompt-function (lambda nil (let ((path (abbreviate-file-name (eshell/pwd)))) (concat (format (propertize "(%s@%s)[%s]\n>" 'face '(:weight bold)) (propertize (user-login-name) 'face '(:foreground "cyan")) (propertize (system-name) 'face '(:foreground "cyan")) (propertize path 'face `(:foreground ,(if (= (user-uid) 0) "red" "green") :weight bold))) " ")))) ;;; If the prompt spans over multiple lines, the regexp should match ;;; last line only. (setq-default eshell-prompt-regexp "^> ") (with-eval-after-load 'em-term (nconc eshell-visual-commands '("abook" "alsamixer" "cmus" "fzf" "htop" "mpsyt" "mpv" "mutt" "ncdu" "newsbeuter" "pinentry-curses" "ranger" "watch")) (setq eshell-visual-subcommands '(("git" "log" "l" "lol" "diff" "d" "dc" "show") ("sudo" "vi")))) ;;; Support for Emacs' pinentry (setq epa-pinentry-mode 'loopback) (pinentry-start) ;;; Alias management possibilities: ;;; - Version eshell-alias and store it in user-emacs-directory. Simplest and ;;; fastest, but aliases cannot be included conditionnaly, e.g. depending on the ;;; existence of a program. ;;; - Store eshell-alias in cache and populate it dynamically on startup. ;; (setq eshell-aliases-file (concat user-emacs-directory "eshell-alias")) ;;; ;;; `eshell/alias' is too slow as it reads and write the file on each definition. ;;; Let's write manually instead. (with-eval-after-load 'em-alias ;;; TODO: This conflicts with `evil-define-key' during the initialization of ;;; the first eshell session: the map in insert-mode will not take the changes ;;; into account. Going to normal mode and back to insert mode works. (eshell-read-aliases-list) (dolist (alias '(("l" "ls -1 $*") ("la" "ls -lAh $*") ("ll" "ls -lh $*") ;; ("ls" "ls -F $*") ;; ("emacs" "find-file $1") ;; ("em" "find-file $*") ("cp" "*cp -i $*") ("mv" "*mv -i $*") ("mkdir" "*mkdir -p $*") ("mkcd" "*mkdir -p $* && cd $1"))) (add-to-list 'eshell-command-aliases-list alias)) (eshell-write-aliases-list)) ;;; Hooks ;;; `nobreak-char-display' makes some output look weird, e.g. with 'tree'. (add-hook 'eshell-mode-hook 'turn-off-nobreak-char-display) (add-hook 'eshell-mode-hook 'eshell-cmpl-initialize) ;;; REVIEW: Emacs' standard functions fail when output has empty lines. ;;; This implementation is more reliable. ;;; Reported at https://debbugs.gnu.org/cgi/bugreport.cgi?bug=27405. (with-eval-after-load 'em-prompt (defun eshell-next-prompt (n) "Move to end of Nth next prompt in the buffer. See `eshell-prompt-regexp'." (interactive "p") (re-search-forward eshell-prompt-regexp nil t n) (when eshell-highlight-prompt (while (not (get-text-property (line-beginning-position) 'read-only) ) (re-search-forward eshell-prompt-regexp nil t n))) (eshell-skip-prompt)) (defun eshell-previous-prompt (n) "Move to end of Nth previous prompt in the buffer. See `eshell-prompt-regexp'." (interactive "p") (backward-char) (eshell-next-prompt (- n)))) (when (require 'bash-completion nil t) ;; REVIEW: `bash-completion-dynamic-complete-0' is almost what we want for eshell, it only needs a tiny modification. ;; Ask upstream to change the function arguments. ;; See https://github.com/szermatt/emacs-bash-completion/issues/13. (defun bash-completion-eshell-complete () (cl-letf (((symbol-function 'comint-line-beginning-position) #'(lambda () (save-excursion (eshell-bol) (point))))) (bash-completion-dynamic-complete))) (defun eshell-set-default-completion () (setq pcomplete-default-completion-function (lambda () ;; TODO: Sometimes `eshell-default-completion-function' does better, e.g. "gcc " shows .c files. ;; Does it matter since we have Helm at hand? ;; TODO: Lot's of things don't complete well, like "go". Use fish if available. (while (pcomplete-here (nth 2 (bash-completion-eshell-complete))))))) ;; `eshell-set-default-completion' must be run after `eshell-cmpl-initialize'. (add-hook 'eshell-mode-hook 'eshell-set-default-completion t)) (provide 'init-eshell)