首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
282 阅读
2
从零开始配置 vim(15)——状态栏配置
166 阅读
3
nvim番外之将配置的插件管理器更新为lazy
158 阅读
4
2018总结与2019规划
144 阅读
5
PDF标准详解(五)——图形状态
119 阅读
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
登录
Search
标签搜索
c++
c
学习笔记
windows
emacs
elisp
文本操作术
编辑器
NeoVim
Vim
win32
读书笔记
VimScript
linux
文本编辑器
投资理财
Java
反汇编
OLEDB
数据库编程
Masimaro
累计撰写
429
篇文章
累计收到
32
条评论
首页
栏目
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
页面
归档
友情链接
关于
搜索到
1
篇与
的结果
2026-09-10
《elisp 每日一练》—— Minor Mode 变量生成机制与状态控制
前面我们以一个简单的 dired-mode 实战案例结束了关于文件和目录专题的学习。现在开始进入Emacs中的Minor Mode 和 Major Mode的学习。define-minor-mode在前面我们已经使用了 define-minor-mode 定义了一个 windows-notepad 的mode,它主要绑定Windows常用的快捷键,例如:C-c 复制、C-v 粘贴。我们定义一个最简单的minor mode(define-minor-mode my/focus-mode "这是一个专注模式。")对于这种使用宏的代码,我们可以使用将光标放在代码块的最后使用 pp-macroexpand-last-sexp 来得到展开之后的内容。上述代码展开之后得到如下内容(progn (progn :autoload-end (defvar-local my/focus-mode nil "Non-nil if My/Focus mode is enabled.\nUse the command `my/focus-mode' to change this variable.")) (defun my/focus-mode (&optional arg) "这是一个专注模式。\n\nThis is a minor mode. If called interactively, toggle the `My/Focus\nmode' mode. If the prefix argument is positive, enable the mode, and if\nit is zero or negative, disable the mode.\n\nIf called from Lisp, toggle the mode if ARG is `toggle'. Enable the\nmode if ARG is nil, omitted, or is a positive number. Disable the mode\nif ARG is a negative number.\n\nTo check whether the minor mode is enabled in the current buffer,\nevaluate the variable `my/focus-mode'.\n\nThe mode's hook is called both when the mode is enabled and when it is\ndisabled." (interactive (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg) 'toggle))) (let ((last-message (current-message))) (setq my/focus-mode (cond ((eq arg 'toggle) (not my/focus-mode)) ((and (numberp arg) (< arg 1)) nil) (t t))) (when (boundp 'local-minor-modes) (setq local-minor-modes (delq 'my/focus-mode local-minor-modes)) (when my/focus-mode (push 'my/focus-mode local-minor-modes))) (run-hooks 'my/focus-mode-hook (if my/focus-mode 'my/focus-mode-on-hook 'my/focus-mode-off-hook)) (if (called-interactively-p 'any) (progn nil (unless (and (current-message) (not (equal last-message (current-message)))) (let ((local " in current buffer")) (message "%s %sabled%s" "My/Focus mode" (if my/focus-mode "en" "dis") local)))))) (force-mode-line-update) my/focus-mode) :autoload-end (defvar my/focus-mode-hook nil) (unless (get 'my/focus-mode-hook 'variable-documentation) (put 'my/focus-mode-hook 'variable-documentation "Hook run after entering or leaving `my/focus-mode'.\nNo problems result if this variable is not bound.\n`add-hook' automatically binds it. (This is true for all hook variables.)")) (put 'my/focus-mode-hook 'custom-type 'hook) (put 'my/focus-mode-hook 'standard-value (list nil)) nil (with-no-warnings (add-minor-mode 'my/focus-mode 'nil (if (boundp 'my/focus-mode-map) my/focus-mode-map) nil nil)))从这段代码的注释上看,它它主要做了以下事情:定义一个名为 my/focus-mode 也就是与mode同名的 buffer-local 的状态变量;定义了一个和 mode 同名为 my/focus-mode 交互式切换函数;定义并配置 hook 变量;把这个 minor mode 注册到 Emacs 的 minor mode 系统里。我们依次来阅读一下这段代码,看看各个部分是如何工作的。首先我们看到 my/focus-mode 函数中的注释,这段注释详细的介绍了这段代码的作用,我们翻译一下得到如下内容这是一个次要模式。如果以交互方式调用该模式,则会切换“My/Focus模式”。如果前缀参数为正数,则启用该模式;如果为负数或零,则禁用该模式。 如果通过Lisp调用该模式,并且ARG的值为“toggle”,则切换该模式。如果ARG为nil、被省略,或者是一个正数,则启用该模式。如果ARG为负数,则禁用该模式。 要检查当前缓冲区中是否启用了该次要模式,可以评估变量`my/focus-mode`的值。 在该模式被启用和禁用时,都会调用相应的钩子函数。从这里可以看出,实际上切换该mode的状态实际上是在修改 my/focus-mode 变量的值。my/foucs-mode 函数下面我们来看一下该函数中的重点操作1. 设置变量值切换mode 状态我们先来看一下最前面的 interactive 它的内容如下:(interactive (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg) 'toggle)))这段代码的意思是如何用户调用时没有参数,那么设置它的值为 'toggle 表示切换。如果有使用 C-u 输入前置参数,那么它会尝试将输入的参数转为数字。后面紧跟着的就是对 my/focus-mode 的赋值,它的代码如下:(setq my/focus-mode (cond ((eq arg 'toggle) (not my/focus-mode)) ((and (numberp arg) (< arg 1)) nil) (t t)))它的赋值规则很简单,如果是 'toggle ,那么对原值取反以便进行切换,如果是数字,负数和0的时候关闭mode,也就是将 my/focus-mode 设置为ni,正数则设置为 t。2. 维护 local-minor-modes接着我们往后看(when (boundp 'local-minor-modes) (setq local-minor-modes (delq 'my/focus-mode local-minor-modes)) (when my/focus-mode (push 'my/focus-mode local-minor-modes)))local-minor-modes 是一个变量,它记录当前buffer中启用了哪些 minor mode。上面的代码先判断该变量是否绑定了值,如果绑定了,则先删除当前mode,后面根据 my/focus-mode 的真假来决定是否将当前mode添加到变量中3. 运行hookhook 的概念我们还没有开始接触,但是如果读者对其他编程语言熟悉,相比对hook应该也不陌生。这里你可以简单的类比事件的回调。它提供了一个外部干预事件的契机。(run-hooks 'my/focus-mode-hook (if my/focus-mode 'my/focus-mode-on-hook 'my/focus-mode-off-hook))从这里我们能看出来,不管启用还是关闭,都会调用 my/foucus-mode-hook,后续如果启用则会调用 my/focus-mode-on-hook。关闭会调用 my/focus-mode-off-hook。4. 刷新mode line 并返回状态函数最后一句代码如下: (force-mode-line-update) my/focus-mode)force-mode-line-update 用来刷新 modeline 的状态,最后对 my/focus-mode 求值并返回它的值。定义并配置 hook 变量函数结束之后,它又定义了一个 my/focus-mode-hook 这个变量我们刚刚看到,它在函数中被使用。(defvar my/focus-mode-hook nil)我们看到该变量没有采用 defvar-local 定义成 buffer-local 类型的变量,而是使用 defvar 定义成了全局变量。Emacs 中的hook变量通常都是全局变量,这个我们在讨论hook的时候再来解释(unless (get 'my/focus-mode-hook 'variable-documentation) (put 'my/focus-mode-hook 'variable-documentation "Hook run after entering or leaving `my/focus-mode'..."))这段代码虽然我们不知道 variable-documentation 是个什么东西,但是从名字上应该能猜出来,它是变量文档。我们通过 C-h v 查看hook变量时显示的内容。它的作用是如果没有指定 hook变量的文档,它帮我们指定一个默认的变量文档。接着我们看到两个 put(put 'my/focus-mode-hook 'custom-type 'hook) (put 'my/focus-mode-hook 'standard-value (list nil))它向 custom 系统注册了 my/focus-mode-hook 的信息,我们可以使用 customize 命令打开 custom 的界面,查到该变量的信息。它们设置变量类型为 hook、标准值为空列表。最后以下面的代码作为结尾:(with-no-warnings (add-minor-mode 'my/focus-mode 'nil (if (boundp 'my/focus-mode-map) my/focus-mode-map) nil nil))我们重点关注 add-minor-mode 部分的代码,它是一个底层C写的函数,负责将该mode注册到Emacs 管理的 minor mode 系统中。它的函数签名为:(add-minor-mode TOGGLE NAME &optional KEYMAP AFTER TOGGLE-FUN)根据函数签名,我们可以看到,注册时填写了如下信息:切换状态的变量为 my/focus-modeNAME 为 nil,也就是不在mode line 上显示mode 的名称KEYMAP 部分的参数表示该mode下的 keymap 。叠加一个if语句,表明如果有名为 my/foucus-mode-map 的keymap则采用它作为keymap,否则keymap部分为 nilAFTER 和 TOGGLE-FUN 两个参数为 nil。总结到此我们描述完了 define-minor-mode 宏在底层所干的事情:定义切换变量,与mode名称相同定义切换函数,与mode名称相同切换时根据传入参数来切换mode状态:为负数或者0表示关闭,1表示开启,'toggle 表示根据当前状态切换切换状态时会无条件执行 my/foucs-mode-hook ,在开启和关闭时则会再额外单独执行 my/foucs-mode-on-hook 或者 my/foucs-mode-off-hook中间它还会根据mode状态维护 local-minor-mode 变量、注册minor mode 到Emacs,更新显示一些内容本节到此就结束了,本节因为主要是理论性的内容,不涉及实操,所以就不进行练习了,如果说非要按照惯例进行练习的话,读者可以自行查看源代码并跟着本文的内容梳理一下思路。或者也可以尝试根据文档使用诸如 :keymap、:init-value 之类的关键字看看它们在背后都做了什么
2026年09月10日
0 阅读
0 评论
0 点赞