Writing GNU Emacs Extensions ch2 要点

Writing GNU Emacs Extensions ch2 要点

在绑定按键之前,可以使用describe-key看看要绑定的键是否已经被绑定到别的命令了。

发现C-x C-n被绑在了set-goal-column上,C-x C-p绑定到了mark-page上,如果把C-x C-n和C-x C-p绑定到"next window"和"previous window"上的话,就会覆盖原有的默认绑定。但是仔细想想,既然那些命令不经常使用,我们无须在意失去了他们的绑定按键,我们总是可以通过M-x使用他们。

通过

(global-set-key "\C-x\C-n" 'other-window)

可以把C-x C-n绑定到other-window上去。可以放到.emacs文件中。

但是想要把C-x C-p绑定到"previous window"上就显得比较困难,因为Emacs中没有这样的命令使得光标移动到先前的window,是时候定义一个了~~

我们知道other-window可以再给定参数为-1时移动光标到先前的window,我们就可以定义一个新的命令——other-window-backward:

(defun other-window-backward ()
"Select the previous window."
(interactive)
(other-window -1))

other-window-backward后面就是parameter列表。(tips:"parameter"和"argument"的区别,这两个术语一般是可以交互使用的,但是技术上说,parameter更多的想表达的是函数中定义的变量,而argument一般指的是函数被调用时传递的值,这个值会被继续赋为parameter。)

在Emacs中的命令指的是能够通过交互使用的lisp函数,并不是所有的lisp函数都是命令,但是所有的命令都是lisp函数。

在函数定义之前使用interactive就能使函数变为命令。

定义完函数就能进行绑定了:

(global-set-key "\C-x\C-p" 'other-window-backward)

上面的键绑定符合我们的要求,但是我们仍然可以进一步提高一下,当使用C-x o或是C-xC-n调用other-window时,你可以指定一个数据前缀n改变函数的表现,C-u 2 C-x C-n表示"移动到跟在当前window的第二个window中"。就像我们看过的,n也可能为负数去反向移动。

对比起来,other-window-backward只能一次移动一个window。

为了改变,我们得参数化我们的函数,我们可以这么做:

(defun otehr-window-backward (n)
"Select Nth previous window."
(interactive "p")
(other-window (- n)))

interactive使用一个参数:一连串字符,一个字符代表一个参数。本例用的字符p表示:如果有一个前缀参数,就把它表示为一个数字,如果没有前缀参数,就当成数字1.

走到这里,其实仍然能够继续提高other-window-backward,就是使参数n变为可选的参数,这样的话,就使得不传递任何参数也成为可能,比如(other-window-backward)。

(defun other-window-backward (&optional n)
"Select Nth previous window."
(interactive "p")
(if n
    (other-window (- n))     ;if n is non-nil
  (other-window -1)))        ;if n is nil

在parameter列表中的&optional关键字表示的所有跟随着的parameters都是可选的。这样的话,这个函数可以被无参调用,这样的话,可选参数就会被置为一个特殊的值nil。

(tips:符号t代表truth。在Lisp解释器中,符号nil和空表()是相同的对象。如果你调用listp,测试符号nil是否为一个列表,一会得到结果t,也就是truth,如果你调用symbolp测试空表是否为一个符号,依然会得到t。然而,如果你对其他列表调用symbolp,或是对其他符号调用listp,你都会得到nil)

如果你对符号nil求值,结果会是nil。正因如此,不像其他的符号,当你想要获得nil的值时不需要使用单引号。比如:

(setq x nil) ;assign nil to varable x
和
(setq x 'nil)
是相同的

虽然两者都是对的,一般意义上,你从来也不要把一个新的值赋为nil(而且emacs也不允许这么做)。

下面咱们看看这段代码:

(if n                  ; if this...
(other-window(- n)) ;...then this
(other-window -1))     ;... else this

这个例子中,我们可以更加简练,观察到在if语句中other-window在两个分支都被调用了,区别仅仅是n,我们可以改写成下面这样:

(other-window (if n (- n) -1))

更一般的来说:

(if test
     (a b)
    (a c))

可以简写为:

(a (if test b c))

我们还能进一步观察到,在if两个分支中,我们在寻找负的某个东西,那么:

(if n (- n) -1)

可以变为:

(- (if n n 1))

一个老lisp程序员的技巧或许可以用得上,可以使上述表达式更加简洁:

(if n n 1) 等同于 (or n 1)

更一般的来说:

(if a a b)

可以被下列代码替换:

(or a b)

事实上,如果a是true,那么(if a a b)会重复计算a两次,而(or a b)不会。(当然,如果你就想计算两次,你当然可以用if)。

(if a a ;if a is true,return a
  (if b b ; else if b is true ,return b
    ...
    (if y y z))) ;else if y is true,return y,else z

可以被写成:

(or a b ... y z)

类似的:

(if a
  (if b
     ...
      (if y z)))

可以被写成:

(and a b ... y z)

and会一直计算每个arguments,知道找到一个nil,如果找到nil,就返回nil,否则就返回最后argument计算的值。

好了,那么接下来,我们的other-window-backward函数变成了下面的样子:

(defun other-window-backward (&optional n)
"Select Nth previous window."
(interactive "p")
(other-window (- (or n 1))))

但是更加Emacs-lisp的样子应该是:

(defun other-window-backward (&optional n)
"Select Nth previous window"
(interactive "P")
(other-window (- (prefix-numeric-value n))))

这个版本中,interactive的参数不再是小写的p了,而是大写的P;

大写的P表示"当被交互的调用时,会让n获得原始的前缀参数"。前缀参数的原始形式在Emacs中是一种数据结构,用来记录用户的调用信息。prefix-numeric-value函数能够恰当的解释这个数据结构成为一个数字。

我们可以使用defalias用另外的一个名字引用一个lisp函数:

(defalias 'scroll-ahead 'scroll-up)
(defalias 'scroll-behind scroll-down)
hooks
一个hook是一个普通的lisp变量,它的值是在特定条件下要执行的一系列函数 列表,举个例子,变量write-file-hooks是一组Emacs当遇到buffer要被保 存时要执行的一系列函数,post-command-hook是执行每个interactive命令 后要执行的一系列函数列表。

假设我想要在我查看文件时,让Emacs使得buffer只读,我有这个需求的话,那么我最感兴趣的可能就是find-file-hooks,这个hook是每次Emacs查看一个新文件时要执行的。(还有很多的hooks。可以使用M-x aprops RET hook RET查看。)

add-hook函数可以对一个hook变量添加函数,下面就是一个要添加到find-file-hooks中的函数:

(defun read-only-if-symlink ()
   (if (file-symlink-p buffer-file-name)
       (progn
          (setq buffer-read-only t)
          (message "File is a symlink"))))

这个函数测试当前的buffer是否是一个symlink(链接到一个文件)。如果是的,那么这个buffer会被值为只读,显示"File is a symlink"消息。

我们仔细看看这个函数:

  • 首先,参数表是空的。在hook变量中出现的函数都是空参数的
  • file-symlink-p函数测试它的参数是否是一个symbolic link。这是个boolean断言,表示 它会返回true或者false,在lisp中,断言一般都在名字末尾有p或者-p
  • file-symlink-p的参数是buffer-file-name。这个预先定义的变量在每个buffer中有不同 的值,它总是当前buffer的名字,
  • 如果buffer-file-name是一个symlink,我们就有两件事情要做:1.使buffer称为只读; 2.显示一个message。然而,lisp中只允许在if的then部分存在一个表达式,如果写成:
    (if (file-symlink-p buffer-file-name)
        (setq buffer-read-only t)
        (message "File is a symlink"))
    

    那就成了,如果buffer-file-name 是一个symlink,就使得buffer只读,否则打印 message。

    所以这里用了progn表达式,progn表达式按顺序计算它的每一个子表达式,然后返回最后 一个。

  • buffer-read-only 是buffer的本地变量,它控制了dangqianbuffer是否是只读的变量。

现在,我们可以定义read-only-if-symlink了,

(add-hook 'find-file-hooks 'read-only-if-symlink)
匿名函数
当使用defun定义一个函数时,你给定了这个函数的名字,这样它可以被很 多地方调用。但是,如果一个函数不需要到处都能调用呢? read-only-if-symlink就是一个很好的例子,它只需要出现在 find-file-hooks列表里,事实上,在find-file-hooks外面调用还很产生很 多的害处。

下面是一个例子:

(lambda ()
(if (file-symlink-p buffer-file-name)
(progn
      (setq buffer-read-only t)
      (message "File is a symlink"))))

在lambda后面的空括号指得是匿名函数的参数表。这个函数没有参数,一个匿名函数的定义可以放在任何你可能想要防止函数名字的地方:

(add-hook 'find-file-hooks
        '(lambda ()
           (if (file-symlink-p buffer-file-name)
           (progn
                 (setq buffer-read-only t)
                   (message "File is a symlink")))))

仍然有一个原因不在hooks中使用匿名函数,如果你想要从一个hook中移除一个函数时,你得用remove-hook:

(remove-hook 'find-file-hooks 'read-only-if-symlink)

当Emacs提示我我正在编辑一个symlink文件,我可能希望更换成链接文件链接的真实文件,或是把这个链接文件换成一份目标文件的拷贝。

(defun visit-target-instead ()
  "Replace this buffer with a buffer visiting the link target."
  (interactive)
  (if buffer-file-name
      (let ((target (file-symlink-p buffer-file-name)))
      (if target
            (find-alternate-file target)
         (error "Not visiting a symlink")))
     (error "Not visiting a file")))
(defun clobber-symlink ()
  "Replace symlink with a copy of the file."
  (interactive)
  (if buffer-file-name
      (let ((target (file-symlink-p buffer-file-name)))
      (if target
            (if (yes-or-no-p (format "Replace %s with %s?
                                      buffer-file-name
                                      target))
         (progn
            (delete-file buffer-file-name)
            (write-file buffer-file-name)))
         (error "Not visiting a symlink")))
     (error "Not visiting a file")))

上面两例都是以(if buffer-file-name)开始的,这个是个必要得测试,因为buffer-file-name可能返回nil(比如"* scratch * buffer"),这样会传nil给file-symlink-p函数,会产生错误("Wrong type argument: stringp,nil"),这个错误一般是函数需要一个string,而传递了一个nil。当buffer-file-name为nil,在if的else部分,error函数会中断Emacs,使Emacs返回top-level,继续用户的下一个举动。

那么为什么read-only-if-symlink里头不需要测试buffer-file-name?因为它只会被find-file-hooks调用。

就像前面讲到的,file-symlink-p是一个断言函数,意味着它返回truth或者false,但是lisp中可以用除nil意外一切表达,所以file-symlink-p当它的参数为一个symlink时它并不返回t,而是返回symlink链接的文件名。

下面换个话题结束本章,介绍下一个非常有用的lisp工具叫做 advice

我在同一时间编辑许多拥有类似文件名称的文件,这样的事情常常发生,比如:foobar.c和foobar.h。当我想从一个buffer切换到另外一个buffer中时,我使用C-x b,switch-to-buffer,它会让我输入一个buffer的名字,我一般会尽量好的敲击键盘,我使用TAB补全buffer的名字,比如:

C-x b fo

TAB会补全"fo"成为"foobar.c",然后我会按RET,90%的情况,这样的工作是很好的方式,但是有的时候,"fo"后面TAB补全,有的时候,只会补全到"foobar.",继续要要求我分辨"foobar.c"和"foobar.h"。然后这样的话,我有的时候就会出错的打开"foobar."。

这样并不是我想要的,然后我不得不杀掉这个新的buffer(C-x k,kill-buffer)。所以我多想Emacs在我要切换到不存在的buffer时提醒我,直到我确认这个举措。

为了实现这个样的功能,我们会使用advice,advice是一部分附着在一个lisp函数上的代码,它可以在函数执行前或者执行后执行。"Before" advice 可以在参数传进函数前影响参数,"after" advice 可以影响函数的返回值。

下面我们试试:

(defadvice switch-to-buffer (before existing-buffer
                                activate compile)
"When interactive, switch to existing buffers only."
(interactive "b"))

我们仔细看看这个代码,defadvice函数创建了一个advice,它的第一个参数是一个存在的函数——switch-to-buffer。接下来是一个特殊格式的列表,第一个参数是是before,这个指明了是before还是after advice,(还有一种类型的advice,叫"around",它能够嵌套进入另外的advice代码里。)再接下来就是advice代码的名字,我把它命名为"existing-buffer",在接下来的就是一些关键字:activate意味着这个advice被激活了(也可以定义一个advice但是不激活:inactive);compile意味着这个advice代码应该被"byte-compiled"来加速。

在这个advice中唯一的内容是interactive声明,替换了switch-to-buffer的interactive声明,原本的switch-to-buffer可以接受任何字符串参数作为buffer的名字,interactive中的b字符意味着:只允许存在的buffer的名字作为参数。

很遗憾,这样做太有限制性了,仍然有可能要切换到不存在的buffer,于是做了以下折中:C-x b拒绝切换到不存在的buffer,C-u C-x b允许。

(defadvice switch-to-buffer (before existing-buffer
                             activate compile)
"When interactive, switch to existing buffers only,
unless given a prefix argument."
  (interactive
    (list (read-buffer "Switch to buffer:
                        (other-buffer)
                        (nill current-prefix-arg)))))

我们用了没见过的interactive的用法,我们传递了一个list而不是一个字符。

read-buffer是一个低级的lisp函数,它提示用户输入一个buffer的名字,低级的意思是所有其他的要用户输入buffer名字的函数都是调用read-buffer来完成的。这个例子中调用的时候带了一个string和两个可选的参数:一个默认的buffer,一个布尔值显示是否强制是存在的buffer。

默认的buffer我们选的是other-buffer的结果,对于布尔值,我们使用

(null current-prefix-arg)

这个语句测试current-pre,如果是,结果会是t,如果不是,结果会是nil。并且,如果没有前缀参数,就是:

(read-buffer "Switch to buffer:"
              (other-buffer)
              t)

意味着:读取一个buffer名字,强制输入为已经存在的buffer,如果有一个前缀参数。那么就是:

(read-buffer "Switch to buffer:"
              (other-buffer)
              nil)

(interactive "P")是返回原始数据,(interactive "p")是返回原始数据的数字值

下面展现了可能的原始值对应的数字值:

if the User Types Raw Value Numeric Value
C-u followed by a (possibly negative) The number itself The Number
C-u - (with no following number) The symbol - -1
C-u n times in a row (with no following number or minus sign ) A list containing the number 4n 4n itself
No prefix argument nil 1

Date: 2014-08-24T14:20+0000

Author: kirchhoff

Org version 7.9.3f withEmacs version 24

Validate XHTML 1.0

你可能感兴趣的:(emacs,emacs,lisp,gnu,gnu,manual,elisp)