Eshell提示符美化

折腾Eshell系列

Updated on 2019-11-04 11:52 (Created on: 2017-09-26 04:01)

折腾Eshell 提示符

前言

近日,笔者在浏览 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.

与原有提示符冲突

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

如果用上这个 sexy 的提示符,eshell-extra-prompt 的额外的信息就不能显示,感觉好亏:(鱼和熊掌我都想要,似乎太贪心了?怎么办,自己去修改eshell_prompt_extra源码 :).

折腾源码

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主题。

定义所需的 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)
" "))

总结

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

Emacs sexy

(时隔两年之后再回首, 不禁感叹, 当初的自己真的喜欢折腾, 精力真的好)Enjoy Emacs, Enjor Tweaking :)