1 发现帅气的提示符

近日,笔者在浏览 Reddit 的时候,发现了一位 Emacs 用户把他的 Eshell 提示符修改得很帅,如图:

本着拿来主义的想法,我就直接把这位小哥的代码添加到了我的配置文件里面:

(setq eshell-prompt-function
      (lambda ()
	(concat
	 (propertize "┌─[" 'face `(:foreground "green"))
	 (propertize (user-login-name) 'face `(:foreground "red"))
	 (propertize "@" 'face `(:foreground "green"))
	 (propertize (system-name) 'face `(:foreground "blue"))
	 (propertize "]──[" 'face `(:foreground "green"))
	 (propertize (format-time-string "%H:%M" (current-time)) 'face `(:foreground "yellow"))
	 (propertize "]──[" 'face `(:foreground "green"))
	 (propertize (concat (eshell/pwd)) 'face `(:foreground "white"))
	 (propertize "]\n" 'face `(:foreground "green"))
	 (propertize "└─>" 'face `(:foreground "green"))
	 (propertize (if (= (user-uid) 0) " # " " $ ") 'face `(:foreground "green"))
	 )))

效果自然是很 sexy.

2 与原有提示符冲突

但是我原来使用的 eshell-prompt-extra 的效果就被覆盖了。而 eshell_prompt_extra 可以提供的额外信息非常多,包括:git, python virtualenv, 以及远程登录时的主机信息,如图:

如果用上这个 sexy 的提示符,eshell-extra-prompt 的额外的信息就不能显示,感觉好亏:(

鱼和熊掌我都想要,似乎太贪心了?怎么办,自己去修改 eshell_prompt_extra源码 :).

3 折腾源码

eshell_prompt_extra 这个包注释加上全部代码也只是 400 行,代码也写得很清晰. 其中大部份是辅助函数,而 Eshell 的提示符效果是通过两个 eshell-theme 函数来实现的。use-package 的配置:

(use-package eshell-prompt-extras
  :ensure t
  :load-path "~/Code/github/eshell-prompt-extras"
  :config (progn
	    (with-eval-after-load "esh-opt"
	      (use-package virtualenvwrapper :ensure t)
	      (venv-initialize-eshell)
	      (autoload 'epe-theme-lambda "eshell-prompt-extras")
	      (setq eshell-highlight-prompt nil
		    eshell-prompt-function 'epe-theme-lambda))
	    ))

epe-theme-lambda 的代码如下:


(defun epe-theme-lambda ()
  "A eshell-prompt lambda theme."
  (setq eshell-prompt-regexp "^[^#\nλ]*[#λ] ")
  (concat
   (when (epe-remote-p)
     (epe-colorize-with-face
      (concat (epe-remote-user) "@" (epe-remote-host) " ")
      'epe-remote-face))
   (when epe-show-python-info
     (when (fboundp 'epe-venv-p)
       (when (and (epe-venv-p) venv-current-name)
	 (epe-colorize-with-face
	  (concat "(" venv-current-name ") ") 'epe-venv-face))))
   (let ((f (cond ((eq epe-path-style 'fish) 'epe-fish-path)
		  ((eq epe-path-style 'single) 'epe-abbrev-dir-name)
		  ((eq epe-path-style 'full) 'abbreviate-file-name))))
     (epe-colorize-with-face (funcall f (eshell/pwd)) 'epe-dir-face))
   (when (epe-git-p)
     (concat
      (epe-colorize-with-face ":" 'epe-dir-face)
      (epe-colorize-with-face
       (concat (epe-git-branch)
	       (epe-git-dirty)
	       (epe-git-untracked)
	       (let ((unpushed (epe-git-unpushed-number)))
		 (unless (= unpushed 0)
		   (concat ":" (number-to-string unpushed)))))
       'epe-git-face)))
   (epe-colorize-with-face " λ" 'epe-symbol-face)
   (epe-colorize-with-face (if (= (user-uid) 0) "#" "")
			   'epe-sudo-symbol-face)
   " "))

代码主要逻辑是调用之前定义的辅助函数,判断是否需要显示 git, python, 远程主机等信息,然后对相应的提示符进行拼接。

而其中出现得比较频繁的 epe-colorize-with-face 就是作者定义的一个宏(macro), 用来显示字符串以及对应的 face(其实就是不同的颜色啦). 看懂了代码就好办了,现在就可以自己添加一个 Eshell 主题。

3.1 定义所需的 face

因为我需要显示的 face(颜色), eshell-extra-prompt 并没有定义,所以就只好自己动手啦:

(defface epe-pipeline-delimiter-face
  '((t :foreground "green"))
  "Face for pipeline theme delimiter."
  :group 'epe)

(defface epe-pipeline-user-face
  '((t :foreground "red"))
  "Face for user in pipeline theme."
  :group 'epe)

(defface epe-pipeline-host-face
  '((t :foreground "blue"))
  "Face for host in pipeline theme."
  :group 'epe)

(defface epe-pipeline-time-face
  '((t :foreground "yellow"))
  "Face for time in pipeline theme."
  :group 'epe)

然后就是按着原有的 Eshell 提示符来组装一个新的 Eshell 主题了,然后把这个主题定义成 pipeline (其实是我自己也没想出比较新颖的名字啦):

(defun epe-theme-pipeline ()
  "A eshell-prompt theme with full path, smiliar to oh-my-zsh theme."
  (setq eshell-prompt-regexp "^[^#\nλ]* λ[#]* ")
  (concat
   (if (epe-remote-p)
       (progn
	 (concat
	  (epe-colorize-with-face "┌─[" 'epe-pipeline-delimiter-face)
	  (epe-colorize-with-face (epe-remote-user) 'epe-pipeline-user-face)
	  (epe-colorize-with-face "@" 'epe-pipeline-delimiter-face)
	  (epe-colorize-with-face (epe-remote-host) 'epe-pipeline-host-face))
	 )
     (progn
       (concat
	(epe-colorize-with-face "┌─[" 'epe-pipeline-delimiter-face)
	(epe-colorize-with-face (user-login-name) 'epe-pipeline-user-face)
	(epe-colorize-with-face "@" 'epe-pipeline-delimiter-face)
	(epe-colorize-with-face (system-name) 'epe-pipeline-host-face)))
     )
   (concat
    (epe-colorize-with-face "]──[" 'epe-pipeline-delimiter-face)
    (epe-colorize-with-face (format-time-string "%H:%M" (current-time))
			    'epe-pipeline-time-face)
    (epe-colorize-with-face "]──[" 'epe-pipeline-delimiter-face)
    (epe-colorize-with-face (concat (eshell/pwd)) 'epe-dir-face)
    (epe-colorize-with-face  "]\n" 'epe-pipeline-delimiter-face)
    (epe-colorize-with-face "└─>" 'epe-pipeline-delimiter-face)
    )
   (when epe-show-python-info
     (when (fboundp 'epe-venv-p)
       (when (and (epe-venv-p) venv-current-name)
	 (epe-colorize-with-face
	  (concat "(" venv-current-name ") ") 'epe-venv-face))))
   (when (epe-git-p)
     (concat
      (epe-colorize-with-face ":" 'epe-dir-face)
      (epe-colorize-with-face
       (concat (epe-git-branch)
	       (epe-git-dirty)
	       (epe-git-untracked)
	       (let ((unpushed (epe-git-unpushed-number)))
		 (unless (= unpushed 0)
		   (concat ":" (number-to-string unpushed)))))
       'epe-git-face)))
   (epe-colorize-with-face " λ" 'epe-symbol-face)
   (epe-colorize-with-face (if (= (user-uid) 0) "#" "")
			   'epe-sudo-symbol-face)
   " "))

4 总结

这样一个新的 Eshell 主题就完工了,然后我给 eshell-extra-prompt 发了一个Pull Request, 最终效果如下:

Enjoy Emacs, Enjor Tweaking :)