Emacs: Tweak Eshell (prompt, aliases, evil bindings, next-prompt defun, helm)

master
Pierre Neidhardt 2017-06-12 21:26:50 +01:00
parent e44a13c3e5
commit c37af9b279
4 changed files with 124 additions and 28 deletions

View File

@ -236,6 +236,7 @@ e-mail."
(add-hook 'dired-mode-hook (lambda () (require 'mode-dired))) (add-hook 'dired-mode-hook (lambda () (require 'mode-dired)))
;;; Eshell ;;; Eshell
(add-to-list 'package-selected-packages 'pcomplete-extension)
(add-hook 'eshell-load-hook (lambda () (require 'mode-eshell))) (add-hook 'eshell-load-hook (lambda () (require 'mode-eshell)))
;;; Evil ;;; Evil

View File

@ -1,47 +1,87 @@
;; Eshell ;;; Eshell
;; This mode has a lot of hooks. ;;; This mode has a lot of hooks.
;; `emacs-load-hook' is run at the very beginning; not all variables/functions will be set. ;;; `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. ;;; `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: (setq
;; - Version eshell-alias and store it in user-emacs-directory. Simplest and eshell-directory-name (concat emacs-cache-folder "eshell")
;; fastest, but aliases cannot be included conditionnaly, e.g. depending on the eshell-banner-message ""
;; existence of a program. eshell-ls-use-colors t
;; - Store eshell-alias in cache and populate it dynamically on startup. eshell-destroy-buffer-when-process-dies t
; (setq eshell-aliases-file (concat user-emacs-directory "eshell-alias")) ;; 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 (setq eshell-prompt-function
(lambda nil (lambda nil
(let ((path (abbreviate-file-name (eshell/pwd)))) (let ((path (abbreviate-file-name (eshell/pwd))))
(format "(%s@%s)[%s]\n%s " (concat
(user-login-name) (format
(system-name) (propertize "(%s@%s)[%s]\n>" 'face '(:weight bold))
path (propertize (user-login-name) 'face '(:foreground "cyan"))
(if (= (user-uid) 0) "#" ">"))))) (propertize (system-name) 'face '(:foreground "cyan"))
;; If the prompt spans over multiple lines, the regexp should match last line only. (propertize path 'face `(:foreground ,(if (= (user-uid) 0) "red" "green") :weight bold)))
(setq eshell-prompt-regexp "^[#>] ") " "))))
;;; If the prompt spans over multiple lines, the regexp should match
;;; last line only.
(setq eshell-prompt-regexp "^> ")
(with-eval-after-load 'em-term (with-eval-after-load 'em-term
(nconc eshell-visual-commands (nconc eshell-visual-commands
'("abook" "cmus" "fzf" "htop" "mpv" "mutt" "ncdu" "newsbeuter" "ranger"))) '("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")))
;; (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 (with-eval-after-load 'em-alias
(eshell-read-aliases-list) (eshell-read-aliases-list)
(dolist (alias '(("ls" "ls -F $*") (dolist (alias '(
;; ("ls" "ls -F $*")
("l" "ls -1 $*") ("l" "ls -1 $*")
("la" "ls -lAh $*") ("la" "ls -lAh $*")
("ll" "ls -lh $*") ("ll" "ls -lh $*")
("grep" "grep --color=auto") ;; ("grep" "grep --color=auto")
("cal" "cal -m $*")
("cp" "cp -i $*")
("mv" "mv -i $*")
("mkdir" "mkdir -p $*") ("mkdir" "mkdir -p $*")
("mkcd" "mkdir -p $* && cd $1") ("mkcd" "mkdir -p $* && cd $1")
("emacs" "find-file $1") ;; ("emacs" "find-file $1")
("em" "find-file $1"))) ("em" "find-file $1")))
(add-to-list 'eshell-command-aliases-list alias)) (add-to-list 'eshell-command-aliases-list alias))
(eshell-write-aliases-list)) (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) (provide 'mode-eshell)

View File

@ -31,15 +31,14 @@
(evil-mode 1) (evil-mode 1)
(evil-leader/set-key (evil-leader/set-key
"RET" 'spawn-terminal "RET" 'eshell
"\\" 'toggle-window-split "\\" 'toggle-window-split
"b" 'buffer-menu "b" 'buffer-menu
"e" 'find-file "e" 'find-file
"k" 'kill-this-buffer "k" 'kill-this-buffer
"o" 'delete-other-windows "o" 'delete-other-windows
"w" 'evil-window-next "w" 'evil-window-next
"|" 'swap-windows "|" 'swap-windows)
)
(when (require 'magit nil t) (when (require 'magit nil t)
;; Use S-SPC instead of SPC to browse commit details. ;; Use S-SPC instead of SPC to browse commit details.
(evil-leader/set-key "v" 'magit-status)) (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 "d" 'package-menu-mark-delete)
(evil-define-key 'normal package-menu-mode-map "x" 'package-menu-execute) (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. ;; Go-to-definition.
;; From https://emacs.stackexchange.com/questions/608/evil-map-keybindings-the-vim-way ;; From https://emacs.stackexchange.com/questions/608/evil-map-keybindings-the-vim-way
(evil-global-set-key (evil-global-set-key

View File

@ -54,6 +54,14 @@
(helm-make-source "Git files" 'helm-ls-git-source (helm-make-source "Git files" 'helm-ls-git-source
:fuzzy-match helm-ls-git-fuzzy-match))) :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'. ;;; 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") (setq helm-grep-git-grep-command "git --no-pager grep -n%cH --color=always --full-name -e %p -- %f")