如何自定义 Emacs 的 mode line

小记一次折腾 Emacs 的过程

Created on 2017-12-14 21:57

前言

相信使用 Vim 的同学都不会对 Powerline 这个插件陌生,这个状态栏插件可以将显示非 常多的有用信息,而对于 Emacs 的同学来说,我们也有 Powerline 这个插件,完美地借 鉴了 Vim Powerline 的特点. Powerline 是 Emacs 的各式各样 mode line 之一。 但是我今天要分享的不是如何去安装折腾 Powerline 这个插件,而是如何去美化和自定 义原生的 mode line

原生 mode line

Emacs 自身就带有 mode line, 只是很难看,而且显示的信息晦涩难懂,所以为了弥补原生 mode line 的缺陷,就出现了类似 smart-mode-line, powerline,spaceline 等等诸多特性完备,功能强大的 mode line. 记得当初玩 Emacs 的时候,每个 mode line 插件我都装过,各种特性我也折腾过,从 Powerline 到Smart-mode-line,最后我选择了 spaceline, 虽说我不用 Spacemacs. 但是最近我在Windows 上面使用 Emacs, Emacs 真 的是卡得不能自理,所以我只能想办法提高 Emacs 的性能。我就将所有不必要的 package 都禁用了,spaceline 自然是其中一位,我就用回了原生的 mode line, 真的是辛辛苦苦 几十年,一夜回到解放前。但是后来我发现原生的 mode line 其实并不难看,功能也强大。 所以我就花了一番工夫定制原生 mode line, 实现性能样式两不误 :)

了解 mode line

Emacs 的文档非常友好,如果想要折腾某项功能,首先可以去查阅一下文档,比如说 mode line, 通过 C-h v 查阅控制 mode-line 样式 mode-line-format 参数,可以得到 mode-line 最初的默认值:

Original value was 
("%e" mode-line-front-space mode-line-mule-info mode-line-client mode-line-modified mode-line-remote mode-line-frame-identification mode-line-buffer-identification "   " mode-line-position
(vc-mode vc-mode)
"  " mode-line-modes mode-line-misc-info mode-line-end-spaces)

看到这么多的变量名,可以猜测 Emacs mode line 上不同的部分应该是由不同的变量控制 的,现在需要做的是找出变量和样式之间的对应关系。

对照截图,按照文档的说明:

  1. mode-line-front-space: 默认展示在 mode-line 的最前面,感觉用处不大 :)
  2. mode-line-mule-info: 显示当前内容的语言环境,编码信息和输入法,看到的那个 U的意思是指这是 UTF-8
  3. mode-line-client: 用来标识这个是不是 emacsclient
  4. mode-line-modified: 用来显示当前展示的内容是否被修改,这个变量有四个值 ** 表示内容已经被修改, -- 表示内容没被修改, %%表示内容只读, %* 表示内容只读并且被修改了。记得当初刚用 Emacs 的时候,我是被这个变量弄晕了,怎么每次显示都不一样的 :(
  5. mode-line-remote: 用来显示当前 buffer 的 default-directory 是否是远程文件夹,感觉用处也不大
  6. mode-line-frame-identification: 用来标识 frame, 但是我平时用一个 frame, 多个 window, 所以也没什么用
  7. mode-line-buffer-identification: 标识当前 buffer, 如果当前 buffer 属于文件则显示文件名,不然就是显示 buffer 名
  8. " ", 空白的地方
  9. mode-line-position: 用来显示当前内容在 buffer 所在的位置,比如说 top,buttom 等,因为图中的内容太少,在当前的聚焦窗口可以全部显示出来,所以显示成 All
  10. mode-line-position: 显示 buffer size, 行号,列号。但是官方文档说这个是可选的(optional)
  11. (vc-mode vc-mode): 显示版本管理信息,比如使用的是 Git, 就显示 Git:master. 只是我什么的 helloworld没有使用版本管理,所以没有显示
  12. mode-line-modes: 显示 minor-mode, major-mode 的相关信息
  13. mode-line-misc-info: 上面已经说过了。
  14. mode-line-end-spaces: 就是那些 ------

删减 mode line

现在已经对 mode-line 有一个感性的认识了,所以现在可以开始折腾 mode line 了。首先 把我觉得没什么用的 mode line样式去掉。比如: mode-line-client,mode-line-remote, mode-line-frame-identification 真的非常 鸡肋,弃之也不可惜。另外我使用的 auto-save 这个插件,键盘三秒之内没有输 入就会自动保存内容,所以 mode-line-modified也可以去掉。此外对于那些诸多的 minor-mode 的信息,我觉得除了显得乱杂,也没有什么大用,也可以去掉这些 minor-mode 的信息。但是这个应该怎么去掉呢,我可不能去掉 mode-line-modes, 因为 那样会把我所有的 major-mode minor-mode 信息都去掉的。引入 diminish包,然后把所 有我不喜欢的 minor-mode 的信息都去掉。至于其他的 minor-mode 的信息,因为我使用了 use-package, 所以我就直接使用 :diminish 关键字去掉

;;; Use Miminish minor modes to change the mode line

(use-package diminish
  :ensure t
  :demand t
  :diminish hs-minor-mode
  :diminish abbrev-mode
  :diminish auto-revert-mode
  :diminish auto-fill-function
  :diminish mail-abbrevs-mode
  :diminish highlight-indentation-mode
  :diminish subword-mode)

;;; Stolen From https://github.com/hrs/dotfiles/blob/master/emacs.d/configuration.org
(defmacro diminish-minor-mode (filename mode &optional abbrev)
  "Macro for diminish minor mode with FILENAME MODE and ABBREV."
  `(eval-after-load (symbol-name ,filename)
     '(diminish ,mode ,abbrev)))

(defmacro diminish-major-mode (mode-hook abbrev)
  "Macro for diminish major mode with MODE-HOOK and ABBREV."
  `(add-hook ,mode-hook
             (lambda () (setq mode-name ,abbrev))))
(diminish-minor-mode 'highlight-indentation 'highlight-indentation-mode )
(diminish-minor-mode 'mail-abbrevs 'mail-abbrevs-mode )
(diminish-minor-mode 'auto-revert 'auto-revert-mode)
(diminish-minor-mode 'simple 'auto-fill-function )
(diminish-minor-mode 'eldoc 'eldoc-mode)
(diminish-major-mode 'emacs-lisp-mode-hook "Elisp")
(diminish-major-mode 'lisp-interaction-mode-hook "λ")
(diminish-major-mode 'python-mode-hook "Py")

(use-package evil-mc
  :ensure t
  :diminish evil-mc-mode
  :commands evil-mode
  :init
  (global-evil-mc-mode t)
  )

修改 mode-line

现在把那些不喜欢的 mode-line 删除了,是时候增加我需要的那些内容了,比如说 evil-mode 的 状态信息,比如说 number-window的坐标信息。默认的 evil-mode 状 态栏是在 mode-line 中间的,但是我喜欢将状态栏放到 mode-line 前面的部分。此外,我 也不喜欢将 major-modeminor-mode 放在一起,我更喜欢 minor-mode 单独放在 一起,并把 major-mode 放到 buffer 名前面

最终效果

按照文档的说明,可以通过修改 mode-line-format 参数来修改 mode-line 样式,现在就来展示一下最终的折腾效果:

完整代码

去掉不需要展示的 minor-mode

;;; Use Miminish minor modes to change the mode line

(use-package diminish
  :ensure t
  :demand t
  :diminish hs-minor-mode
  :diminish abbrev-mode
  :diminish auto-revert-mode
  :diminish auto-fill-function
  :diminish mail-abbrevs-mode
  :diminish highlight-indentation-mode
  :diminish subword-mode)

;;; Stolen From https://github.com/hrs/dotfiles/blob/master/emacs.d/configuration.org
(defmacro diminish-minor-mode (filename mode &optional abbrev)
  "Macro for diminish minor mode with FILENAME MODE and ABBREV."
  `(eval-after-load (symbol-name ,filename)
     '(diminish ,mode ,abbrev)))

(defmacro diminish-major-mode (mode-hook abbrev)
  "Macro for diminish major mode with MODE-HOOK and ABBREV."
  `(add-hook ,mode-hook
             (lambda () (setq mode-name ,abbrev))))
(diminish-minor-mode 'highlight-indentation 'highlight-indentation-mode )
(diminish-minor-mode 'mail-abbrevs 'mail-abbrevs-mode )
(diminish-minor-mode 'auto-revert 'auto-revert-mode)
(diminish-minor-mode 'simple 'auto-fill-function )
(diminish-minor-mode 'eldoc 'eldoc-mode)
(diminish-major-mode 'emacs-lisp-mode-hook "Elisp")
(diminish-major-mode 'lisp-interaction-mode-hook "λ")
(diminish-major-mode 'python-mode-hook "Py")

修改 evil-mode 状态栏

;;;Move evil tag to beginning of mode line
(setq evil-mode-line-format '(before . mode-line-front-space))
;;; modify evil-state-tag
(setq evil-normal-state-tag   (propertize "[Normal]")
      evil-emacs-state-tag    (propertize "[Emacs]")
      evil-insert-state-tag   (propertize "[Insert]")
      evil-motion-state-tag   (propertize "[Motion]")
      evil-visual-state-tag   (propertize "[Visual]")
      evil-operator-state-tag (propertize "[Operator]"))

调整 mode-line 宽度,增加顶部和底部显示效果

(defun samray/set-mode-line-width ()
  "Set mode line width, it is so cool."
  (set-face-attribute 'mode-line nil
		      :box '(:line-height 0)))
(defvar after-load-theme-hook nil
  "Hook run after a color theme is loaded using `load-theme'.")
(defadvice load-theme (after run-after-load-theme-hook activate)
  "Run `after-load-theme-hook'."
  (run-hooks 'after-load-theme-hook))
(add-hook 'after-load-theme-hook #'samray/set-mode-line-width)

调整 mode-line

;;; customize mode line
(setq-default mode-line-format '("["
				 "%e"
				 (:eval
				  (window-numbering-get-number-string))
				 "]"
				 mode-line-front-space
				 mode-line-mule-info
				 ;; mode-line-client
				 ;; mode-line-modified -- show buffer change or not
				 ;; mode-line-remote -- no need to indicate this specially
				 ;; mode-line-frame-identification -- this is for text-mode emacs only
				 "["
				 mode-name
				 ":"
				 mode-line-buffer-identification
				 "]"
				 " "
				 mode-line-position
				 (vc-mode vc-mode)
				 " "
				 ;; mode-line-modes -- move major-name above
				 "["
				 minor-mode-alist
				 "]"
				 mode-line-misc-info
				 ;; mode-line-end-spaces
				 ))

参考

mode-line-format