ambevar-dotfiles/.emacs.d/lisp/init-eshell.el

278 lines
11 KiB
EmacsLisp
Raw Normal View History

;;; Eshell
2016-10-14 14:03:30 +02:00
;;; 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'.
;; Emacs pinentry for GPG.
(require 'main)
2017-11-05 12:39:37 +01:00
;;; REVIEW: If history contains read-only text (e.g. accidental insertion of the prompt),
;;; `eshell-write-history' won't work.
;;; See #29153.
2017-10-23 21:06:08 +02:00
;;; REVIEW: ANSI coloring goes wrong sometimes. Quite often with emerge/eix.
;;; Fixed in #27407.
(require 'patch-eshell)
;;; TODO: Sometimes transmission-daemon does not start from Eshell.
2017-11-05 12:39:37 +01:00
;;; REVIEW: Redirecting big output to file (e.g. /dev/null) is extremely slow.
;; > cat /usr/share/dict/british-english | wc -l
;;; The above line yields rancom results. Plus it's much slower than
;; > wc -l /usr/share/dict/british-english
;;; while it should only cost an additional process to launch.
2017-11-05 12:39:37 +01:00
;;; See #29154.
;;; REVIEW: Cannot "C-c C-c" during a `sudo pacman -Syu`. A bug was filed about that already.
;;; TODO: The buffer stutters when writing "in-place", e.g. pacman, git.
;;; It seems that it does not do it as much in `emacs -Q`.
;;; REVIEW: `eshell/sudo' should not read -u in command arguments.
;;; This fails: sudo pacman -Syu --noconfirm.
;;; http://www.gnu.org/software/emacs/manual/html_node/eshell/Built_002dins.html
;;; https://emacs.stackexchange.com/questions/5608/how-to-let-eshell-remember-sudo-password-for-two-minutes
;;; See http://debbugs.gnu.org/cgi/bugreport.cgi?bug=27411
;;; and #28323.
2017-07-28 09:12:25 +02:00
;;; REVIEW: Eshell/Shell completion fails when PATH has a non-readable element.
;;; See https://github.com/emacs-helm/helm/issues/1785
;;; and https://debbugs.gnu.org/cgi/bugreport.cgi?bug=27300.
;;; REVIEW: 40M+ output: Stack overflow in regexp matcher
;;; See bug#28329.
;;; I guess the chunking is significant too. Could you try saving
;;; the output chunks, with this:
;;
;; (defvar eshell-chunk-number 0)
;; (defconst eshell-output-chunk-dir "eshell-output")
;; (make-directory eshell-output-chunk-dir t)
;;
;; (defun catch-eshell-output-chunk ()
;; (write-region eshell-last-output-block-begin
;; eshell-last-output-end
;; (format "%s/chunk.%d"
;; eshell-output-chunk-dir
;; eshell-chunk-number)
;; nil :quiet)
;; (setq eshell-chunk-number (1+ eshell-chunk-number)))
;;
;; (add-hook 'eshell-output-filter-functions
;; 'catch-eshell-output-chunk)
;;;
;;; And then afterwards 'cat eshell-output/chunk.*' should hopefully
;;; reproduce it?
;;; REVIEW: Eshell mixes stderr and stdout it seems.
;;; Example:
;;; $ mu find --nocolor --sortfield=d --maxnum=500 flag:unread AND NOT flag:trashed >/dev/null
;;; mu: no matches for search expression (4)
;;; See #21605 "24.3; Eshell not using stderr".
2017-07-28 09:12:25 +02:00
2017-11-05 12:39:37 +01:00
;;; REVIEW: Some parsing fails
;;; > echo -n $PATH | sed 's/:[^:]*sophos[^:]*/:/g'
;;; :s/:]*sophos[/:]*/:/"/
;;; Unknown modifier character /
;;;
;;; > date +%Z
2017-11-05 12:39:37 +01:00
;;; See #29157.
;;; TODO: Change face of eshell/* commands in commandline to avoid confusion
;;; with system programs.
;;; TODO: Merge/re-use documentation of eshell/* commands with their docstring.
;;; TODO: Hour is printed twice. We don't need to set this?
;; (setq eshell-ls-date-format (replace-regexp-in-string "^\\+*" "" (getenv "TIME_STYLE")))
;;; TODO: ls: Sort using locale.
2017-11-05 12:39:37 +01:00
;;; REVIEW: `kill -#' does not work.
;;; See #29156.
(setq eshell-directory-name (concat emacs-cache-folder "eshell"))
2017-06-17 18:37:04 +02:00
;;; Use native 'sudo', system sudo asks for password every time.
(require 'em-tramp)
2017-11-05 12:39:37 +01:00
(with-eval-after-load 'esh-module
2017-06-17 18:37:04 +02:00
;; 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
2017-06-27 22:13:51 +02:00
eshell-history-size 1024
eshell-hist-ignoredups t
eshell-destroy-buffer-when-process-dies t)
;;; 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
(when eshell-status-p
(propertize (or (eshell-status-display) "") 'face font-lock-comment-face))
(format
(propertize "(%s@%s)" 'face '(:weight bold))
(propertize (user-login-name) 'face '(:foreground "cyan"))
(propertize (system-name) 'face '(:foreground "cyan")))
(if (and (require 'magit nil t) (or (magit-get-current-branch) (magit-get-current-tag)))
(let* ((root (abbreviate-file-name (magit-rev-parse "--show-toplevel")))
(after-root (substring-no-properties path (min (length path) (1+ (length root))))))
(format
(propertize "[%s/%s@%s]" 'face '(:weight bold))
(propertize root 'face `(:foreground ,(if (= (user-uid) 0) "orange" "gold")))
(propertize after-root 'face `(:foreground ,(if (= (user-uid) 0) "red" "green") :weight bold))
(or (magit-get-current-branch) (magit-get-current-tag))))
(format
(propertize "[%s]" 'face '(:weight bold))
(propertize path 'face `(:foreground ,(if (= (user-uid) 0) "red" "green") :weight bold))))
(propertize "\n>" 'face '(: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
(dolist (p '("abook" "alsamixer" "cmus" "fzf" "htop" "mpsyt" "mpv" "mutt" "ncdu" "newsbeuter" "pinentry-curses" "ranger" "watch" "wifi-menu"))
(add-to-list 'eshell-visual-commands p))
(setq eshell-visual-subcommands
2017-12-12 22:49:18 +01:00
;; Some Git commands use a pager by default.
;; Either invoke the subcommands in a term ("visual") or configure Git
;; to disable the pager globally.
;; '(("git" "log" "diff" "show")
'(("sudo" "wifi-menu") ; Arch Linux
("sudo" "vi" "visudo"))))
;;; 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.
;;; Note: Evil has fixed some issues in the meantime. Also test with `evil-define-key*'.
2017-10-21 11:35:03 +02:00
;;;
;;; If we read the alias list here, it means we make commandline-defined aliases persistent.
;; (eshell-read-aliases-list)
(dolist
(alias
'(("l" "ls -1 $*")
("la" "ls -lAh $*")
("ll" "ls -lh $*")
2017-10-23 21:06:08 +02:00
;; TODO: Aliasing eshell/{cp,mv,ln} does not work.
;; TODO: "sudo" does not work on aliases.
;; See bug #27168.
;; REVIEW: Eshell/TRAMP's sudo does not work with aliases.
;; See #28320.
2017-10-21 11:35:03 +02:00
;; ("ls" "ls -F $*") ; not supported
;; ("emacs" "find-file $1")
2017-10-23 21:06:08 +02:00
;; ("cp" "eshell/cp -iv $*")
2017-10-21 11:35:03 +02:00
;; ("mv" "eshell/mv -iv $*")
2017-10-23 21:06:08 +02:00
("cpv" "cp -iv $*")
2017-10-22 11:37:51 +02:00
("mvv" "mv -iv $*")
("rmv" "rm -v $*")
("md" "eshell/mkdir -p $*")
2017-12-03 10:49:32 +01:00
("mkcd" "eshell/mkdir -p $* ; cd $1"))) ; TODO: Does not work because mkdir exits with nil?
(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)
2017-10-22 21:19:12 +02:00
;;; History
2017-06-27 22:13:51 +02:00
;;; Filter out space-beginning commands from history.
;;; TODO: history/command hook: trim surrounding space. Check `eshell-rewrite-command-hook'.
;;; TODO: history: do not save failed commands to file.
2017-10-22 21:19:12 +02:00
;;; TODO: history: do not store duplicates. Push unique command to front of the list.
2017-06-27 22:13:51 +02:00
(setq eshell-input-filter
(lambda (str)
(not (or (string= "" str)
(string-prefix-p " " str)))))
2017-10-22 21:19:12 +02:00
;;; Shared history.
(defvar eshell-history-global-ring nil
"The history ring shared across Eshell sessions.")
(defun eshell-hist-use-global-history ()
"Make Eshell history shared across different sessions."
(unless eshell-history-global-ring
(when eshell-history-file-name
(eshell-read-history nil t))
(setq eshell-history-global-ring (or eshell-history-ring (make-ring eshell-history-size))))
2017-10-22 21:19:12 +02:00
(setq eshell-history-ring eshell-history-global-ring))
(add-hook 'eshell-mode-hook 'eshell-hist-use-global-history)
;;; Spawning
(defun eshell-or-new-session (&optional arg)
"Create an interactive Eshell buffer.
Switch to last Eshell session if any.
Otherwise create a new one and switch to it.
See `eshell' for the numeric prefix ARG."
(interactive "P")
(if (or arg (eq major-mode 'eshell-mode))
(eshell (or arg t))
(let ((last (buffer-list)))
(while (and last
(not (with-current-buffer (car last)
(eq major-mode 'eshell-mode))))
(setq last (cdr last)))
(if last
(switch-to-buffer (car last))
(eshell (or arg t))))))
;;; Auto-suggestion
(when (require 'esh-autosuggest nil t)
(setq esh-autosuggest-delay 0.5)
(add-hook 'eshell-mode-hook 'esh-autosuggest-mode)
(define-key esh-autosuggest-active-map (kbd "<tab>") 'company-complete-selection)
(when (require 'helm-config nil t)
(define-key company-active-map (kbd "M-p") 'helm-eshell-history)))
;;; Extra execution information
(defvar eshell-status-p t
"If non-nil, display status before prompt.")
(defvar eshell-status--last-command-time nil)
(make-variable-buffer-local 'eshell-status--last-command-time)
(defvar eshell-status-min-duration-before-display 1
"If a command takes more time than this, display its duration.")
(defun eshell-status-display ()
(when eshell-status--last-command-time
(let ((duration (time-subtract (current-time) eshell-status--last-command-time)))
(setq eshell-status--last-command-time nil)
(when (> (time-to-seconds duration) eshell-status-min-duration-before-display)
(format "#[STATUS] End time %s, duration %.3fs\n"
(format-time-string "%F %T" (current-time))
(time-to-seconds duration))))))
(defun eshell-status-record ()
(setq eshell-status--last-command-time (current-time)))
(add-hook 'eshell-pre-command-hook 'eshell-status-record)
;;; Detach
(when (require 'package-eshell-detach nil t)
(defun eshell-detach-set-keys ()
(define-key eshell-mode-map (kbd "C-c C-z") 'eshell-detach-stop)
(define-key eshell-mode-map (kbd "S-<return>") 'eshell-detach-send-input)
(define-key eshell-mode-map (kbd "C-<return>") 'eshell-detach-attach))
(add-hook 'eshell-mode-hook 'eshell-detach-set-keys))
(provide 'init-eshell)