《elisp 每日一练》——Major Mode 初步探索

《elisp 每日一练》——Major Mode 初步探索

Masimaro
2026-09-16 / 0 评论 / 0 阅读 / 正在检测是否收录...

在前面的文章中,我们详细分析了关于 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 的名称
  • NAME Major 的字符串名称
  • 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-tablelocal-abbrev-table 设置当前的 syntax table 和 abbrev table
  • 最后才运行对应的hook

总结

从这里我们看到,设计上 major mode 一个buffer 有且只有一个,所以在设计上就比较简单粗暴,它不考虑多个mode并存或者切换的场景,它简单的清理前任留下来的东西,直接加入自己的。所以它的代码比minor mode 要简单的多。但是它涉及到的概念可一点也不比 minor mode 少,除了hook、keymap 这些我们经常见到的内容,还有 syntax table、abbrev table和继承这类概念。这些我会在后面的文章中一一介绍

0

评论 (0)

取消