diff --git a/.emacs.d/init.el b/.emacs.d/init.el index 31a14c97..f6898a1e 100644 --- a/.emacs.d/init.el +++ b/.emacs.d/init.el @@ -236,6 +236,7 @@ e-mail." (add-hook 'dired-mode-hook (lambda () (require 'mode-dired))) ;;; Eshell +(add-to-list 'package-selected-packages 'pcomplete-extension) (add-hook 'eshell-load-hook (lambda () (require 'mode-eshell))) ;;; Evil diff --git a/.emacs.d/lisp/mode-eshell.el b/.emacs.d/lisp/mode-eshell.el index 6140a77a..5c272b50 100644 --- a/.emacs.d/lisp/mode-eshell.el +++ b/.emacs.d/lisp/mode-eshell.el @@ -1,47 +1,87 @@ -;; Eshell +;;; Eshell -;; This mode has a lot of hooks. -;; `emacs-load-hook' is run at the very beginning; not all variables/functions will be set. -;; `emacs-mode-hook' is run once everything is loaded. +;;; This mode has a lot of hooks. +;;; `emacs-load-hook' is run at the very beginning; not all variables/functions will be set. +;;; `emacs-mode-hook' is run once everything is loaded. -(setq eshell-directory-name (concat emacs-cache-folder "eshell")) +;;; TODO: Bind "ls"? No need if we have Ctrl-e? +;; (local-set-key "\C-l" 'eshell/ls) -;; 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")) +(setq + eshell-directory-name (concat emacs-cache-folder "eshell") + eshell-banner-message "" + eshell-ls-use-colors t + eshell-destroy-buffer-when-process-dies t + ;; TODO: Hour is printed twice. We don't need to set this? + ;; 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)))) - (format "(%s@%s)[%s]\n%s " - (user-login-name) - (system-name) - path - (if (= (user-uid) 0) "#" ">"))))) -;; If the prompt spans over multiple lines, the regexp should match last line only. -(setq eshell-prompt-regexp "^[#>] ") + (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 eshell-prompt-regexp "^> ") (with-eval-after-load 'em-term (nconc eshell-visual-commands - '("abook" "cmus" "fzf" "htop" "mpv" "mutt" "ncdu" "newsbeuter" "ranger"))) -;; (with-eval-after-load 'em-term -;; (add-to-list 'eshell-visual-subcommands '("git" "log" "diff" "show"))) + '("abook" "cmus" "fzf" "htop" "mpv" "mutt" "ncdu" "newsbeuter" "ranger")) + (add-to-list 'eshell-visual-subcommands '("git" "log" "diff" "show"))) -;; eshell/alias is too slow as it reads and write the file on each definition. +;;; 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. +;;; TODO: Add pacman functions from fish config. +;;; TODO: Compare system tools and lisp equivalents of ls and grep. (with-eval-after-load 'em-alias (eshell-read-aliases-list) - (dolist (alias '(("ls" "ls -F $*") + (dolist (alias '( + ;; ("ls" "ls -F $*") ("l" "ls -1 $*") ("la" "ls -lAh $*") ("ll" "ls -lh $*") - ("grep" "grep --color=auto") + ;; ("grep" "grep --color=auto") + ("cal" "cal -m $*") + ("cp" "cp -i $*") + ("mv" "mv -i $*") ("mkdir" "mkdir -p $*") ("mkcd" "mkdir -p $* && cd $1") - ("emacs" "find-file $1") + ;; ("emacs" "find-file $1") ("em" "find-file $1"))) (add-to-list 'eshell-command-aliases-list alias)) (eshell-write-aliases-list)) +;;; Emacs' standard function fail when output has empty lines. +;;; This implementation is more reliable. +;;; TODO: Report upstream +(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) + (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))) + (provide 'mode-eshell) diff --git a/.emacs.d/lisp/tool-evil.el b/.emacs.d/lisp/tool-evil.el index 06f64b94..cc3c9cfa 100644 --- a/.emacs.d/lisp/tool-evil.el +++ b/.emacs.d/lisp/tool-evil.el @@ -31,15 +31,14 @@ (evil-mode 1) (evil-leader/set-key - "RET" 'spawn-terminal + "RET" 'eshell "\\" 'toggle-window-split "b" 'buffer-menu "e" 'find-file "k" 'kill-this-buffer "o" 'delete-other-windows "w" 'evil-window-next - "|" 'swap-windows - ) + "|" 'swap-windows) (when (require 'magit nil t) ;; Use S-SPC instead of SPC to browse commit details. (evil-leader/set-key "v" 'magit-status)) @@ -159,6 +158,54 @@ (evil-define-key 'normal package-menu-mode-map "d" 'package-menu-mark-delete) (evil-define-key 'normal package-menu-mode-map "x" 'package-menu-execute) +;; Eshell +(defun evil/eshell-insert () + (interactive) + (when (get-text-property (point) 'read-only) + (eshell-next-prompt 1)) + (evil-insert 1)) +(defun evil/eshell-interrupt-process () + (interactive) + (eshell-interrupt-process) + (evil-insert 1)) +(defun evil/eshell-define-keys () + (with-eval-after-load 'tool-helm + (evil-define-key '(normal insert) eshell-mode-map "\C-r" 'helm-eshell-history) + (evil-define-key 'insert eshell-mode-map "\C-e" 'helm-find-files)) + (evil-define-key 'normal eshell-mode-map "i" 'evil/eshell-insert) + (evil-define-key 'normal eshell-mode-map "\M-k" 'eshell-previous-prompt) + (evil-define-key 'normal eshell-mode-map "\M-j" 'eshell-next-prompt) + (evil-define-key 'normal eshell-mode-map "0" 'eshell-bol) + (evil-define-key 'normal eshell-mode-map (kbd "RET") 'eshell-send-input) + (evil-define-key 'normal eshell-mode-map (kbd "C-c C-c") 'evil/eshell-interrupt-process) + (evil-define-key '(normal insert) eshell-mode-map "\M-h" 'eshell-backward-argument) + (evil-define-key '(normal insert) eshell-mode-map "\M-l" 'eshell-forward-argument)) +(add-hook 'eshell-mode-hook 'evil/eshell-define-keys) + +;; TODO: When point is on "> ", helm-find-files looks up parent folder. Prevent that. + +;; DONE: eshell-mode-map gets reset on new shells. Make it permanent. Hook? Hook looks good: +;; https://stackoverflow.com/questions/11946113/emacs-eshell-how-to-read-content-of-command-line-on-pressing-ret + +;; TODO: Cannot kill emacs when eshell has started: "text is read only" + +;; TODO: Make Evil commands react more dynamically with read-only text. +;; Add support for I, C, D, S, s, c*, d*, R, r. +(defun evil/eshell-delete-whole-line () + (interactive) + (if (not (get-text-property (line-beginning-position) 'read-only)) + (evil-delete-whole-line (line-beginning-position) (line-end-position)) + (eshell-return-to-prompt) ; Difference with eshell-bol? + (evil-delete-line (point) (line-end-position)))) +;; (evil-define-key 'normal eshell-mode-map "dd" 'evil/eshell-delete-whole-line) +(defun evil/eshell-change-whole-line () + (interactive) + (if (not (get-text-property (line-beginning-position) 'read-only)) + (evil-change-whole-line (line-beginning-position) (line-end-position)) + (eshell-return-to-prompt) ; Difference with eshell-bol? + (evil-change-line (point) (line-end-position)))) +;; (evil-define-key 'normal eshell-mode-map "cc" 'evil/eshell-change-whole-line) + ;; Go-to-definition. ;; From https://emacs.stackexchange.com/questions/608/evil-map-keybindings-the-vim-way (evil-global-set-key diff --git a/.emacs.d/lisp/tool-helm.el b/.emacs.d/lisp/tool-helm.el index 475e1132..20580308 100644 --- a/.emacs.d/lisp/tool-helm.el +++ b/.emacs.d/lisp/tool-helm.el @@ -54,6 +54,14 @@ (helm-make-source "Git files" 'helm-ls-git-source :fuzzy-match helm-ls-git-fuzzy-match))) +;;; Eshell +(add-hook + 'eshell-mode-hook + (lambda () + (eshell-cmpl-initialize) + (define-key eshell-mode-map [remap eshell-pcomplete] 'helm-esh-pcomplete) + (define-key eshell-mode-map (kbd "C-r") 'helm-eshell-history))) + ;;; Do not exclude any files from 'git grep'. (setq helm-grep-git-grep-command "git --no-pager grep -n%cH --color=always --full-name -e %p -- %f")