Writing GNU Emacs Extensions ch4 要点

来源:互联网 发布:淘宝商城玩具店 编辑:程序博客网 时间:2024/06/13 13:38

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 withEmacs version 24

Validate XHTML 1.0
0 0
原创粉丝点击