定义 minor mode 的几点说明

发布于 2023-05-08 12:52:50 字数 5174 浏览 44 评论 0

今天的帖子技术含量很高,但我想分享一个几天前的有趣故事。我使用经典架构定义了一个 minor mode

  (defun cool-start ()
    "Start the cool-mode.")

  (defun cool-stop ()
    "Stop the cool-mode.")

  (define-minor-mode my-cool-minor-mode
    "Toggle a mode for doing cool stuff."
    :init-value nil
    :lighter " 8-)"
    (if my-cool-minor-mode
        (cool-start)
      (cool-stop)))

我很好奇是否可以使用前缀参数来决定在启动模式时做什么。这似乎有点冒险,因为前缀参数已经用于决定打开或关闭模式了。

实际上,my-cool-minor-mode 命令的参数的文档点误导人。以下是Emacs手册中的一段摘录:

  • 如果你直接调用mode命令而不带任何前缀参数(通过 M-x,或通过绑定到一个键并输入该键 […]),就会切换 minor-mode。minor-mode 若关闭则打开,若打开则关闭。
  • 如果你使用前缀参数调用mode命令,则若参数为0或负数,minor-mode将无条件关闭;否则,将被无条件地打开。
  • 如果通过 Lisp 调用 mode 命令,则若参数被省略或为 nil,则 minor-mode 将无条件打开。这使得通过 major mode 的 mode hook 来打开 minor-mode 变得很容易 [...]。非 nil 参数的处理方式类似于前面描述的交互式前缀参数。

以下是 Elisp 手册中的一段话:

toggle 命令接受一个可选的(前缀)参数。如果交互方式调用则没有参数的情况下,它切换打开或关闭模式,正的前缀参数启用该模式,任何其他前缀参数将禁用该模式。

在Lisp中调用的话,toggle 参数切换模式,而省略或 nil 参数启用该模式。例如,这可以使通过 major mode 的 mode hook 来打开 minor mode 变得很容易.

如果 DOC 为 nil,则宏提供一个默认的文档字符串来解释上述内容。

正如你所见,从 Elisp 手册中并不完全清楚是否可以通过代码提供数值参数(显然,您不能交互地提供 toggle 参数)。

原来, define-minor-mode 宏以一种非常复杂的方式定义了负责打开和关闭模式的函数。

首先,该宏包含了下面片段:

  (interactive (list (or current-prefix-arg 'toggle)))

这意味着没有参数(在交互调用时)与 'toggle 参数完全等价。另一个技巧是:

  (,@setter
   (if (eq arg 'toggle)
       (not ,getter)
     ;; A nil argument also means ON now.
     (> (prefix-numeric-value arg) 0)))

define-minor-mode 在前面将 setter 设置成了 (setq <mode-name>), 将 getter 设置成了 <mode-name>
这里有一个非常聪明的技巧: getter 变量其实就是mode,而 setter 是将 getter 变量设置为某个值的Elisp form的开头部分。

如果您认为这太抽象,让我告诉您,我的描述还算是简化过得:例如,对于全局模式来说, settergetter 就跟这里说的有所不同。

正如您从上面的技巧中看到的,我的原始问题有一个简单的答案:是的,我们可以在 Elisp 代码中提供数值参数,实际上,提供一个负参数是通过编程手段关闭模式的唯一方法。

了解了这一点,我们现在可以着手解决最初的问题了。既然任何正的前缀参数都意味着“打开模式”,那么我们可以使用它的实际值来做各种事情吗?

答案当然是肯定的。我们可以有两个选择。首先,我们的模式主体可以检查 current-prefix-arg。此外我们还可以使用 arg.(define-minor-mode 宏就是通过该参数调用新定义的切换函数的)。
后者显然没有那么明显(特别是它依赖于的实现细节可能在另一个 Emacs 版本中更改),所以让我们忘记它吧。实际上,与其使用 arg 符号,不如使用一些类似于Common Lisp gensym 的东西。Elisp没有该函数。cl 包有 cl-gensym, 但是内置的特性并不依赖于 cl 包,这是可以理解的。

但是故事还没结束呢。 还有一些关键字参数在 define-minor-mode 的 docstring 和手册中都尚未提及。其中一个就是 :extra-args

  • 有趣的旁注:该 :extra-args 关键字在整个Emacs源中只提到了三次。根据 git blame 的结果,其中一次是定义,最后一次由 Stefan Monnier 在2012-06-10使用。
  • 第二次在 use-hard-newlines minor mode中,最后一次由 Stefan Monnier 在 2001-10-30 使用。
  • 第三次是在 global-font-lock-mode 的注释中提及,该注释写的诗 What was this ~:extra-args thingy for? --Stef~ ,最后一次在2009-09-13使用,作者你是猜猜是谁?

All these arguments (including the first one) are optional, and you can't supply the :extra-args on an interactive call. You can, however, supply them from Elisp code. Here is an example.

你可以这样使用它。在 :extra-args 关键字之后的值应该是一个(unqouted 的)列表,这个列表会被添加到打开本模式的函数的第一个参数之后(例如,在以交互方式调用 mode 命令的前缀参数)。

所有这些参数(包括第一个参数)都是可选的,您不能在交互式调用中提供 :extra-args。但是,你可以通过Elisp代码提供这些参数。例如:

(define-minor-mode my-cool-minor-mode
  "Toggle a mode for doing cool stuff."
  :init-value nil
  :lighter " 8-)"
  :extra-args (cool-arg-1 cool-arg-2)
  (if my-cool-minor-mode
      (progn
        (cool-start)
        (message "cool-arg-1: %s, cool-arg-2: %s" cool-arg-1 cool-arg-2))
    (cool-stop)))

试着执行: M-:(my-cool-min -mode 1 "this") 看看 cool-arg-1 是否变成了 "this" , cool-arg-2 变成 nil。

最后一个关于 define-minor-mode 的趣闻是 My-Cool minor mode 在当前buffer启用时 的默认消息。我注意到 message 函数放到mode的启动代码中时,该默认消息不会现实。

为了找到原因,一开始我在Emacs源码中搜索单词 enabled (2774 处) 和 disabled (1324 处)。没有结果。

然后我 回想起了 debug-on-message 变量, 结果发现我的搜索完全没有。原因是(简化了一点):

(message
  "Some-mode %sabled"
  (if mode-variable "en" "dis"))

好吧,这让我哭笑不得(特别是我必须要处理一点软件国际化的事情),但我承认它在某种程度上是个不错的做法。

更有趣的是,如果负责初始化(或关闭)模式的代码提供了自己的 message,那么 enable/disable 消息实际上是会关闭的。这是在 current-message 函数的帮助下完成的,该函数返回当前在 echo 区域中显示的内容。

总之,我只是简单地了解了下表层。如果你深入文件 easy-mmode.el(所有代码都在该文件中),你会发现相当多的细节(比如 easy-mmode-pretty-mode-name 有数十行代码但只做一件事情那就是将mode符号转换成方便人阅读的形式--使用mode的 lighter 参数来推断大写形式)。)

这是 Emacs 开发人员非常关注细节的另一个例子,即使在开发过程中存在一些有问题的实践。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

与往事干杯

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

eins

文章 0 评论 0

世界等同你

文章 0 评论 0

毒初莱肆砂笔

文章 0 评论 0

初雪

文章 0 评论 0

miao

文章 0 评论 0

qq_zQQHIW

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文