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
Org version 7.9.3f withEmacs version 24
Validate XHTML 1.0- Writing GNU Emacs Extensions ch3 要点
- Writing GNU Emacs Extensions ch1 要点
- Writing GNU Emacs Extensions ch2 要点
- Writing GNU Emacs Extensions ch4 要点
- Writing BREW Extensions
- Writing Use Case Extensions
- Writing BREW Extensions
- Writing R Extensions
- GNU/Emacs
- Emacs - Writing HTML
- GNU Emacs 命令列表
- GNU emacs学习资料
- Learnning GNU Emacs
- Debug Tutorial Part 4: Writing WINDBG Extensions
- GNU Emacs 札记 Emacs的基本概念
- GNU Emacs 中文处理说明
- emacs use Gnu GLOBAL system
- [转载]GNU Emacs 常用快捷键
- Centos 5 安装GIT
- Linux下的多线程编程
- 在Eclipse中远程调试Hadoop
- mtk modem编译
- MIME协议(四) -- MIME消息的头字段 .
- Writing GNU Emacs Extensions ch3 要点
- 代码缺陷静态分析工具FindBugs插件安装使用图解
- Vert.x安装指南
- BZOJ 1015: [JSOI2008]星球大战starwar
- 如何永久性开启adb 的root权限
- 2048 游戏中滑块上下左右滑动相加的逻辑实现
- 黑马程序员—Java中的JDK1.5新特性之注解
- Spring @Transactional工作原理
- 省市区三级联动菜单(附数据库)