Writing GNU Emacs Extensions ch4 要点

37 篇文章 3 订阅
6 篇文章 0 订阅

Writing GNU Emacs Extensions ch4 要点

(defcustom xxx "%x"
  "*Format for \\[insert-date]")

这样可以通过set-variable定制xxx变量的内容,此外还能通过describe-variable获得格式化的doc-string内容。

(defcustom insert-time-format "%X"
"*Format for \\[insert-time]")
(defcustom insert-date-format "%x"
"*Format for \\[insert-date]")
(defun insert-time ()
  "Insert the current time according to insert-time-format."
  (interactive "*");;如果是只读的buffer,报错
  (insert (format-time-string insert-time-format
                                (current-time))))
(defun insert-date ()
   "Insert the current date according to insert-date-format"
  (interactive "*")
  (insert (format-time-string insert-date-format
                               (current-time))))

光光是插入时间,几乎很没用。如果能够在文件写入系统时为文件记录时间就好了。

首先,我们需要在每次文件保存时调用我们的更新写入时间戳的代码,就像第二章所讲的那样,最好的方式是在一个hook变量里添加一个函数。为了找到合适的hook变量,使用M-xapropos RET hook RET,我们找到了四个hook变量:after-save-hook,local-write-file-hooks,write-contents-hooks和write-file-hooks。

我们可以立即抛弃after-save-hook,因为顾名思义,我们并不想在文件保存后再执行我们的代码。

剩下的候选项的差别很微妙:

write-file-hooks
任何buffer的每一次保存都会执行代码。
local-write-file-hooks
一种本地buffer的write-file-hooks。与write-file-hooks 关注每个buffer的变化情况不同,local-write-file-hooks只针对单独的buffer,所 以,如果你想在保存一个lisp文件时执行一种函数,而在保存txt文件时执行另外一个 函数,那么就该使用local-write-file-hooks。
write-contents-hooks
就像local-write-file-hooks那样,这个hooks是针对本地 buffer的,而且在每次buffer保存的时候执行代码。然而,write-contents-hooks中 的函数针对的是buffer的内容,而其他两个hook是针对的是文件是否已编辑。在实践 中,这个意思就是如果你改变了buffer的major mode,那么也就意味着你改变了buffer内容 的理解方式,然后write-contents-hooks返回nil但是local-write-file-hooks并不。 此外,如果你改变Emacs查看的文件,比如键入 set-visited-file-name ,然后 local-write-file-hooks会返回nil,而write-contents-hooks并不会。

于是乎,我们排除了write-file-hooks,此外,我们也排除了write-contents-hooks,因为我们想我们选择的hook对major mode改变的现象免疫。那么就剩下local-write-file-hooks了。

好了,下面得写我们的函数了。这个函数的基本能力应该是能够定位时间戳,删除它,然后写个新的时间戳,最直截了当的方式是让我们的时间戳有个很显著的特征:

WRITESTAMP((12:19pm 7 Jul 96))

那么更新函数可以写成:

(add-hook 'local-write-file-hooks 'update-writestamps)
(defun update-writestamp ()
  (interactive)
  (save-excursion
    (save-restriction
      (save-match-data
        (widen)
        (goto-char (point-min))
        (while (search-forward "WRITESTAMP((" nil t)
            (let ((start (point)))
              (search-forward "))")
              (delete-region start (- (point) 2))
              (goto-char start)
              (insert-date))))))
  nil)

tips:save-excursion保存光标的移动,save-restriction保存buffer的变窄变宽,save-match-data保存搜索的结果(不想因为我们的函数修改了全局的匹配的数据)。widen是撤销任何变窄的影响,使整个buffer都能在处理范围内。(search-forward"WRITESTAMP((" nil t")这里nil表名search的范围是没有边界的(到buffer的末尾),后面会细讲,t的意思是如果没有找到,那么search-forward应该简单地返回nil。(如果没有t,然后还找不到匹配项的话,search-forward会发出错误信号,强制退出当前的命令),如果搜索成功,point会移动到匹配项的后一个字符。search-forward就会返回那个位置。update-writestamp函数的返回值是save-excursion的返回值,为nil。Lisp函数的返回值是函数体的最后一个表达式的值,这里我们强制update-writestamp的返回值为nil,是因为在local-write-file-hooks中的函数返回值是特殊处理的。一般情况下,一个hook变量中的函数返回值是多少是不需要介意的,但是在local-write-file-hooks中的函数值中,一个非nil的返回值意味着,"这个hook函数已经完成了所有的事情了",也就是说如果hook函数返回非nil,在hook变量中的其余函数都不会被调用了,并且,Emacs也不会在hook函数运行结束后保存buffer到文件中。

一般化Writestamps

上面的函数确实让writestamps运行了,但是仍有一些问题,首先,"WRITESTAMP(("和"))"并不是所有的用户都喜欢,其次,用户可能不想用insert-date。

这些问题好解决,我们可以用三个新的变量:

(defvar writestamp-format "%C")
(defvar writestamp-prefix "WRITESTAMP((")
(defvar writestamp-suffix "))")
(defun update-writestamp ()
  (interactive)
  (save-excursion
    (save-restriction
      (save-match-data
        (widen)
        (goto-char (point-min))
        (while (search-forward writestamp-prefix nil t)
            (let ((start (point)))
              (search-forward writestamp-suffix)
              (delete-region start (match-beginning 0));;search完毕后,匹配第一个
              (goto-char start)
              (insert (format-time-string writestamp-format
                                          (current-time))))))))
  nil)

接下来讲讲正则表达式,可以参见http://dsec.pku.edu.cn/~rli/WiKi/EmacsRegexp.html

既然使用了正则表达式,那么就有使用了正则表达式搜索:re-search-forward,那么就有:

(re-search-forward (concat "^" writestamp-prefix) ... )
(re-search-forward (concat writestam-suffix "$") ... )

但是上述表达是有点问题的,因为可能用户的设定的writestamp-prefix或者writestamp-suffix可能也有关于正则的特殊字符,比如writestamp-suffix为'.',那么上述表达式实际上就是搜索行末为除了换行符以外的所有字符。所以我们得把用户设定的"魔幻字符"变为"朴素字符"。比如使用(regexp-quote ".")会返回"\\."。

所以新的update-writestamp函数为:

(defun update-writestamp ()
  "Find writestamps and replace them with the current time."
  (save-excursion
    (save-restriction
      (save-match-data
        (widen)
        (goto-char (point-min))
        (while (re-search-forward
                (concat "^"
                        (regexp-quote writestamp-prefix)) nil t)
          ...))))
  nil)

下面我们要完成这个update-writestamp函数,那么re-search-forward之后,我们需要知道当前行以writestamp-suffix结尾。但是我们不能简单地写成:

(re-search-forward (concat (regexp-quote writestamp-suffix)
                            "$"))

因为它能匹配很多行,而我们唯一感兴趣的是前缀匹配的当前行。一种做法是限制在当前行搜索,search-forward和re-search-forward的第二个可选参数就是干这个事情的,如果不是nil,那它就表示buffer的搜索截止位置,所以新的update-writestamps可以写成:

(defun update-writestamp ()
  "Find writestamps and replace them with the current time."
  (save-excursion
    (save-restriction
      (save-match-data
        (widen)
        (goto-char (point-min))
        (while (re-search-forward
                (concat "^"
                        (regexp-quote writestamp-prefix))
                nil t)
          (let ((start (point)))
            (if (re-search-forward
                 (concat (regexp-quote writestamp-suffix) "$")
                 (save-excursion
                   (end-of-line)
                   (point))
                 t)
                (progn
                  (delete-region start (match-beginning 0))
                  (goto-char start)
                  (insert (format-time-string writestamp-format
                                              (current-time))))))))))
  nil)

正则表达式的威力

实际上,找一个前缀,然后检查当前行是否能匹配后缀,并替换文本的这一系列操作可以减少到两个表达式:

(re-search-forward (concat "^"
                           (regexp-quote wrx)
                           "\\(.*\\)"
                           (regexp-quote writestamp-suffix)
                           "$"))
(replace-match (format-time-string writestamp-format
                                   (current-time))
               t t nil 1)

第二个表达式是replace-match,它能够根据上一轮的搜索结果决定替换一些还是所有的结果:

(replace-match new-string
               preserve-case
               literal
               base-string
               subexpression)

第一个参数是一个要插入的新的字符串,剩下的参数都是可选的:

preserve-case
我们设置为t,为了让replace-match保持大小写敏感。
literal
设置为t,意味着逐字对待new-string,如果为nil,replace-match会使用特 殊的语法解释new-string(参见describe-function replace-match)。
base-string
置为nil,意味着"修改当前buffer",如果该值为一个字符串,那么 replace-match会在这个串里进行替换,而不是整个buffer。
subexpression
置为1,意味着替换第一个,而不是所有的匹配的\\( \\)。

所以,update-writestamps被修改为:

(defun update-writestamps ()
"Find writestamps and replace them with the current time."
  (save-excursion
     (save-restriction
        (save-match-data
          (widen)
          (goto-char (point-min))
          (let ((regexp (concat "^"
                                (regexp-quote writestamp-prefix)
                                "\\(.*\\) "
                                (regexp-quote writestamp-suffix)
                                "$")))
            (while (re-search-forward regexp nil t)
                (replace-match (format-time-string writestamp-format (current-time))
                                t t nil 1))))))
  nil)
(add-hook 'after-change-functions remember-change-time nil t)

nil为占位符,t表示只改变当前本地buffer的一份after-change-functions的拷贝。

(defvar last-change-time nil)
(make-variable-buffer-local 'last-change-time)

使last-change-time称为本地buffer的变量。

(defun foo (a b &rest c)
................)

&rest参数使得所有的后继变量都存在变量c里。比如调用(foo 1 2 3 4),那么a为1,b为2,c为(3 4)列表。

Date: 2014-09-07T22:00+0800

Author: kirchhoff

Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值