在前面的文章中,我们详细分析了关于 minor mode 对于各种资源的管理,最后以一个修改宏增加参数的例子结束了关于minor mode 的话题。今天我们正式进入有关 Major Mode 的话题
背景知识
与Minor Mode 不同,一个buffer 只能有一个Major Mode。
我们可以使用宏 define-derived-mode 来定义 Major Mode。你可能会感觉很奇怪,为什么minor mode 的定义是使用 define-minor-mode 而 Major Mode 是不是使用 define-major-mode 来定义了。这是因为 Major Mode 引入了继承机制。
(define-derived-mode CHILD PARENT NAME [DOCSTRING] [KEYWORD-ARGS...] &rest BODY)CHILD新定义的Major Mode 的名称,与 define-minor-mode 的第一个参数相同PARENT父类Major Mode 的名称NAMEMajor 的字符串名称KEYWORD-ARGS可选的关键字参数对,用于精细控制模式的初始化行为
我们还是用一个最简单的例子看看它展开之后是什么样子的
(define-derived-mode my/foucs-mode nil "My Focus")展开之后,显示如下内容:
(progn
(defvar my/foucs-mode-hook nil)
(unless (get 'my/foucs-mode-hook 'variable-documentation)
(put 'my/foucs-mode-hook 'variable-documentation
"Hook run after entering `my/foucs-mode'.\nNo problems result if this variable is not bound.\n`add-hook' automatically binds it. (This is true for all hook variables.)"))
(unless (boundp 'my/foucs-mode-map)
(put 'my/foucs-mode-map 'definition-name 'my/foucs-mode))
(with-no-warnings (defvar my/foucs-mode-map (make-sparse-keymap)))
(unless (get 'my/foucs-mode-map 'variable-documentation)
(put 'my/foucs-mode-map 'variable-documentation
(purecopy "Keymap for `my/foucs-mode'.")))
(progn
(defvar my/foucs-mode-syntax-table)
(unless (boundp 'my/foucs-mode-syntax-table)
(put 'my/foucs-mode-syntax-table 'definition-name 'my/foucs-mode)
(defvar my/foucs-mode-syntax-table (make-syntax-table)))
(unless (get 'my/foucs-mode-syntax-table 'variable-documentation)
(put 'my/foucs-mode-syntax-table 'variable-documentation
(purecopy "Syntax table for `my/foucs-mode'."))))
(progn
(defvar my/foucs-mode-abbrev-table)
(unless (boundp 'my/foucs-mode-abbrev-table)
(put 'my/foucs-mode-abbrev-table 'definition-name 'my/foucs-mode)
(defvar my/foucs-mode-abbrev-table
(progn
(define-abbrev-table 'my/foucs-mode-abbrev-table nil)
my/foucs-mode-abbrev-table)))
(unless (get 'my/foucs-mode-abbrev-table 'variable-documentation)
(put 'my/foucs-mode-abbrev-table 'variable-documentation
(purecopy "Abbrev table for `my/foucs-mode'."))))
(if (fboundp 'derived-mode-set-parent)
(derived-mode-set-parent 'my/foucs-mode 'nil)
(put 'my/foucs-mode 'derived-mode-parent 'nil))
nil
(defun my/foucs-mode nil
"Major-mode.\nUses keymap `my/foucs-mode-map', abbrev table\n`my/foucs-mode-abbrev-table' and syntax-table\n`my/foucs-mode-syntax-table'.\n\nThis mode runs the hook `my/foucs-mode-hook', as the final or\npenultimate step during initialization.\n\n\\{my/foucs-mode-map}"
(interactive)
(delay-mode-hooks
(kill-all-local-variables) (setq major-mode 'my/foucs-mode)
(setq mode-name "My Focus") nil
(use-local-map my/foucs-mode-map)
(set-syntax-table my/foucs-mode-syntax-table)
(setq local-abbrev-table my/foucs-mode-abbrev-table))
(run-mode-hooks 'my/foucs-mode-hook)))这段内容看起来比 define-minor-mode 生成的要简短一些。我们还是来看看它每一步都做了什么:
- 首先定义并文档化了一系列的变量: hook、mode—keymap、syntax-table、abbrev-table
- 通过
derived-mode-set-parent记录mode间继承的父子关系 - 定义了一个对应的 mode 函数
请注意,这个函数与 minor mode 不同,它并不是一个切换的函数,也没有定义记录 mode 开启或者关闭的标志变量。这是因为Major Mode 本身在设计时就不支持随意的关闭或者开启。从设计上,每个buffer 必须有一个,但是最多只能有一个 Major Mode, Major Mode 是不支持随意开关的,如果想要替换Major Mode,它是强制清空上一个Major Mode 的所有设置并加载新的。所以每次调用这个mode函数实质上是强行占领了之前mode 的位置。
有了这个前提,我们再来看看这个函数具体的操作。
函数所有的代码都被包裹在 delay-mode-hooks 宏中,这个宏可以保证里面的所有代码在hook之前运行。
- 通过
kill-all-local-variables清理当前buffer的所有 buffer local 变量 - 记录当前 major mode 到变量
major-mode中 - 设置变量
mode-name为我们传入的mode 的 use-local-map来给mode安装快捷键映射set-syntax-table和local-abbrev-table设置当前的 syntax table 和 abbrev table- 最后才运行对应的hook
总结
从这里我们看到,设计上 major mode 一个buffer 有且只有一个,所以在设计上就比较简单粗暴,它不考虑多个mode并存或者切换的场景,它简单的清理前任留下来的东西,直接加入自己的。所以它的代码比minor mode 要简单的多。但是它涉及到的概念可一点也不比 minor mode 少,除了hook、keymap 这些我们经常见到的内容,还有 syntax table、abbrev table和继承这类概念。这些我会在后面的文章中一一介绍
评论 (0)