Writing GNU Emacs Extensions ch3 要点

来源:互联网 发布:landsat8数据打开 编辑:程序博客网 时间:2024/05/18 04:00

Writing GNU Emacs Extensions ch3 要点

Table of Contents

  • 1 协作命令
    • 1.1 症状
    • 1.2 一个解决办法
      • 1.2.1 声明变量
      • 1.2.2 保存和还原point
      • 1.2.3 窗口外观
    • 1.3 总结
      • 1.3.1 改进这个命令
      • 1.3.2 符号属性
      • 1.3.3 markers
      • 1.3.4 更多高效的考虑

1 协作命令

本章介绍如何使多个不同的命令一起工作,命令之间如何共享信息。

1.1 症状

Emacs虽然拥有undo功能,可以使你撤销改变,但是它不能撤销简单地导航。

1.2 一个解决办法

假设,我们按错了C-v(the scroll-up command),Emacs会想:用户可能按错了,所以我该记录一些撤销信息,以备不时之需。然后我们得写一个函数,unscroll,能够撤销上一次scroll。

事实上,这样做还不够,如果你在一行里按了好几次C-v,那么一次unscroll应该撤销所有的,而不是上一次按的C-v,这就意味着按键序列中的第一个C-v应该被记录位置,但是我们如何安排呢?在我们记录开始位置之前,我们得测试(a) 下一个命令还会是scroll-up,或者(b)上一个命令不是scroll-up。显然,(a)不可能,我们不能预知未来,幸运的是,(b)很简单,Emacs维护了一个变量叫last-command,这个变量是我们将要用来命令之间通讯信息的第一个机制。

现在问题来了:我们如何把我们的额外代码附着到scroll-up命令上呢?advice!!!嗯,对的,我们需要before advice,因为只有在scroll-up前,我们才知道开始的位置。

1.2.1 声明变量

我们先创建一个全局变量,unscroll-to,它用来存储"undo"信息,他能简单的定位光标应该移动到buffer的位置:

(defvar unscroll-to nil  "Text position for next call to 'unscroll'.")

全局变量是不需要声明的,但是这里使用defvar声明变量是有优势的:

  • 使用defvar可以在声明的变量后面跟上文档字符串
  • 可以给定默认值,这个例子中是nil 设置变量默认值的两种做饭,defvar和setq是不一样的,不像setq可以无条件的给变量赋 值,defvar只有在变量本身还没有值的时候才赋值。

    为什么这个很重要?假设你的.emacs文件包括下列语句:

    (setq mail-signature t)

    意味着当你通过Emacs发送邮件的时候,你希望附上你的签名文件。每当你打开Emacs, mail-signature都会被设置为t,但是但是那个包含发送邮件代码的lisp文件——sendmail 还没有被加载,它只会在你第一次调用mail命令的时候加载进来,当你这么做了,Emacs 会执行:

    (defvar mail-signature nil ...)

    这句话表明nil是mail-signature的默认值,但是因为你已经把mail-signature设定了一 个值,并且不想sendmail覆盖你的设定。你就可以这样做。

  • 被defvar定义的变量可以使用很多tag相关的命令。tags在编程项目中是一种快速查找变 量和函数定义的一种方法。Emacs的tag功能,比如find-tag命令,可以寻找到任意被 def…定义的函数(defun,defalias,defmacro,defvar,defsubst,defconst,defadvice).
  • 当你使用字节编译代码时,byte-compiler如遇到有变量没有被defvar声明的变量时,会 发出警告。

1.2.2 保存和还原point

(defadvice scroll-up (before remember-for-unscroll                      activate compile)"Remember where we started from,for 'unscroll'."(if (not (eq last-command 'scroll-up-command))     (setq unscroll-to (point))))

有了上面的代码,接下来定义unscroll代码就很简单了:

(defun unscroll ()  "Jump to location specified by 'unscroll-to'."   (interactive)   (goto-char unscroll-to))

1.2.3 窗口外观

(defvar unscroll-point nil)(defvar unscroll-window-start nil)(defadvice scroll-up-command (before remember-for-unscroll                             activate compile)  (if (not (eq last-command 'scroll-up-command))      (progn        (setq unscroll-to (point))        (setq unscroll-window-start (window-start)))))(defun unscroll ()  (interactive)  (goto-char unscroll-to)  (set-window-start nil unscroll-window-start))

Set-window-start takes two arguments. The first argument is the window whose start position is being set. If nil is passed as the first argument (as in this example), set-window-start defaults to the currentlyselected window. (Window objects for passing to set-window-start can be obtainedfrom such functions as get-buffer-window and previous-window.)

1.3 总结

我们希望统一unscroll,它可以撤销任何方向的滚动。

很明显的做法是advice scroll-down:

(defadvice scroll-down (before remember-for-unscroll                       activate compile)"Remember where we started from, for 'unscroll'"(if (not (eq last-command 'scroll-down-command))    (setq unscroll-point (point)          unscroll-window-start (window-start)          unscroll-hscroll (window-hscroll))))

如果交替按下上滚,下滚怎么办呢?是回退所有的滚动码?我更倾向于后一种方案,所以我们要测试上一个命令式scroll-up或是scroll-down;

(defadvice scroll-up (before remember-for-unscroll                       activate compile)"Remember where we started from ,for 'unscroll'"(if (not (or (eq last-command 'scroll-up-command)             (eq last-command 'scroll-down-command)))     (setq unscroll-point (point)           unscroll-window-start (window-start)           unscroll-hscroll (window-hscroll))))(defadvice scroll-down (before remember-for-unscroll                          activate compile)"Remember where we startd from ,for 'unscroll'"(if (not (or (eq last-command 'scroll-up-command)             (eq last-command 'scroll-down-command)))   (setq unscroll-point (point)         unscroll-window-start (window-start)         unscroll-hscroll (window-hscroll))))

现在再加上左右的代码:

(defadvice scroll-up (before remember-for-unscroll                       activate compile)"Remember where we started from ,for 'unscroll'"(if (not (or (eq last-command 'scroll-up-command)             (eq last-command 'scroll-down-command)             (eq last-command 'scroll-left-command) ;new             (eq last-command 'scroll-right-command))) ;new     (setq unscroll-point (point)           unscroll-window-start (window-start)           unscroll-hscroll (window-hscroll))))(defadvice scroll-down (before remember-for-unscroll                          activate compile)"Remember where we startd from ,for 'unscroll'"(if (not (or (eq last-command 'scroll-up-command)             (eq last-command 'scroll-down-command)             (eq last-command 'scroll-left-command)  ;new             (eq last-command 'scroll-right-command))) ;new   (setq unscroll-point (point)         unscroll-window-start (window-start)         unscroll-hscroll (window-hscroll))))(defadvice scroll-left (before remember-for-unscroll                          activate compile)"Remember where we startd from ,for 'unscroll'"(if (not (or (eq last-command 'scroll-up-command)             (eq last-command 'scroll-down-command)             (eq last-command 'scroll-left-command) ;new             (eq last-command 'scroll-right-command))) ;new   (setq unscroll-point (point)         unscroll-window-start (window-start)         unscroll-hscroll (window-hscroll))))(defadvice scroll-right (before remember-for-unscroll                          activate compile)"Remember where we startd from ,for 'unscroll'"(if (not (or (eq last-command 'scroll-up-command)             (eq last-command 'scroll-down-command)             (eq last-command 'scroll-left-command) ;new             (eq last-command 'scroll-right-command))) ;new   (setq unscroll-point (point)         unscroll-window-start (window-start)         unscroll-hscroll (window-hscroll))))

1.3.1 改进这个命令

有两点可以改进,第一,既然每个adavice都一样,我们可以共享一个函数:

(defun unscroll-maybe-remember ()(if (not (or (eq last-command 'scroll-up)             (eq last-command 'scroll-down)             (eq last-command 'scroll-left)             (eq last-command 'scroll-right)))       (setq unscroll-point (point)             unscroll-window-start (window-start)             unscroll-hscroll (window-hscroll))))(defadvice scroll-up (before remember-for-unscroll                             activate compile)"Remember where we started from, for 'unscroll'"(unscorll-maybe-remmber))(defadvice scroll-down (before remember-for-unscroll                             activate compile)"Remember where we started from, for 'unscroll'"(unscorll-maybe-remmber))(defadvice scroll-left (before remember-for-unscroll                             activate compile)"Remember where we started from, for 'unscroll'"(unscorll-maybe-remmber))(defadvice scroll-right (before remember-for-unscroll                             activate compile)"Remember where we started from, for 'unscroll'"(unscorll-maybe-remmber))

第二,与其测试n种last-command的可能性,并且都是测试"上一个命令是不是不可滚动的",那么如果能够维护一个值,就好了。

让我们选"unscrollable"这个符号表示所有的不可滚动命令。我们可以把unscroll-maybe-remember变成下面这样:

(defun unscroll-maybe-remember ()   (setq this-command 'unscrollable)   (if (not (eq last-command 'unscrollable))       (setq unscroll-point (point)             unscroll-window-start (window-start)             unscroll-hscroll (window-hscroll))))

所有调用unscroll-maybe-remember的函数会导致this-command变成unscrollable.(great!!!!!!)

1.3.2 符号属性

现在的unscroll-maybe-remember工作的很好了,但是仍然有一些地方值得改进。首先:this-command和last-command并不是我们自己私有变量,他们处于Emacs Lisp 解释器的中心,并且很多Emacs的部件都用到了这个值。我们希望有一个单一的能够区分所有非滚动命令的一个值。

这就迎来了符号属性,符号除了能表示变量,函数定义,还能表示一个属性列表,符号列表里存有键值对。

属性可以由put函数和get函数进行存取,举个例子,如果我们给a-symbol的some-property属性值17:

(put 'a-symbol 'some-property 17)

那么:

(get 'a-symbol 'some-property)

会返回17,如果我们要获取一个没有的属性的值,结果会是nil

所以,我们可以用unscrollable属性改写上面的那些代码,就像:

(put 'scroll-up 'unscrollable t)(put 'scroll-down 'unscrollable t)(put 'scroll-left 'unscrollable t)(put 'scroll-right 'unscrollable t)

那么现在,(get x unscrollable)这个表达式中,当x等于scroll-up,scroll-down,scroll-left,或者scroll-right时,会返回true。其他的符号会返回nil。

所以可以把unscroll-maybe-remember中的:

(if (not (eq last-command unscrollable))...)

改成:

(if (not (get last-command 'unscorllable))...)

而且,我们不需要赋unscrollable给this-command了:

(defun unscroll-maybe-remember ()(if (not (get last-command 'unscrollable))(setq unscroll-point (point)unscroll-window-start (window-start)unscroll-hscroll (window-hscroll))))

1.3.3 markers

如何才能让这个代码更完美呢?加入你不经意地scroll-down了几次,正要unscroll时,你发现了一些文本要修改,于是你修改了它们,然后你unscroll,但是结果可能并不正确了。

就是因为在修改文本的时候,unscroll-point和unscroll-window-start会变化。

更好的方法是使用marker,一个marker能够定义一个buffer的特殊位置,如果buffer的位置因为添加删除而移动了,marker也会相应的移动。

所以:

(defvar unscroll-point (make-marker)"Cursor position for next call to 'unscroll'")(defvar unscroll-window-start (make-marker)"Windows start for next call to 'unscroll'")

使用make-marker函数创建空的marker对象。

set-marker函数是用来设定marker的:

(defun unscroll-maybe-remember ()  (if (not (get last-command 'unscrollable))       (progn          (set-marker unscroll-point (point))          (set-marker unscroll-window-start (window-start))          (setq unscroll-hscroll (window-hscroll)))))

我们不用设置unscroll-hscroll,是因为unscroll-hscroll的值并不是一个buffer位置。

我们不需要修改unscroll函数,是因为goto-char和set-window-start函数也可以处理marker。所以代码还是如下:

(defun unscroll ()"Revert to 'unscroll-point' and 'unscroll-window-start'."(interactive)(goto-char unscroll-point)(set-window-start nil unscroll-window-start)(set-window-hscroll nil unscroll-hscroll))

1.3.4 更多高效的考虑

每个markder对象指向buffer的某个位置,而且会在每次文本插入或删除时更新。

一般的,如果你想取消某个marker对象(意味着不想再用它的值了),最好是

(set-marker xx nil)

Date: 2014-08-29T07:16+0000

Author: kirchhoff

Org version 7.9.3f withEmacs version 24

Validate XHTML 1.0
0 0
原创粉丝点击