精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-07-02
剪切和存储文本当使用'kill'命令剪切文本时,Emacs将它存储到一个列表中,可以用'yank'命令重新获取到。 存储文本到列表当文本被剪切出缓冲区时,它将被存储到一个list中。文本块连续的存储在list中,这个列表看如下面的形式: <src lang="lisp" piece="" previous="" text="" of="" a=""></src> 函数cons可以添加文本块到list,如: (cons "another piece"执行上面的语句,回显区将显示 ("another piece" "a piece of text" "previous piece")使用car和nthcdr函数,可以获取到list中任意的一个文本块。。例: (car (nthcdr 1 '("another piece" 当然,Emacs中实际处理这些时更复杂一些。Emacs中编写的剪切函数能猜想出你需要的是list的哪个元素。 包含这些文本块的list被称作kill ring。 zap-to-char完整的zap-to-char实现这个函数将移除光标和指定的字符之间的文本。被移除的文本被放入kill ring中,可以用C-y(yank)获取到。如果命令带了数字前缀参数n(C-u),它将移除当前光标位置至遇到的第n个字符之间的文本。 如果指定的字符不存在,zap-to-char将显示"Search failed"。 为了决定要移除多少文本,zap-to-char使用了search函数。搜索在文本处理代码中使用得非常广泛。 下面是zap-to-char在Emacs 19中的完整代码: (defun zap-to-char (arg char) ; version 19 implementation interactive语句zap-to-char的interactive语句如下: (interactive "*p\ncZap to char: ")引号中的部分"*p\ncZap to char: ",指定了3个不同的东西。第一,星号,如果当前缓冲区是只读缓冲区将产生一个错误信息。这意味着如果将zap-to-char用于只读缓冲将得到错误信息"Buffer is read-only"。 在Emacs21的实现中没有包含星号。函数与Emacs19中一样能工作,但在只读缓冲区中它不会移除文本,它将复制文本并将文本放到kill ring中。在这种情况下,两个版本中都将显示错误信息。 在Emacs19中的实现也能从只读缓冲区中复制文本,这只是interactive的一个Bug。interactive的文档中说明了,星号将阻止zap-to-char函数对只读缓冲区做任何操作,这个函数不应该复制文本到kill ring中。 在Emacs21中interactive的实现是正确的。因此星号不得不被移除。如果你在这个这个函数的定义中插入了星号,并重新执行函数定义,下次你再在只读缓冲区上运行zap-to-char时,将不能再复制文本到kill ring里。 从这点来看,两个版本中的zap-to-char是一致的。 " " zap-to-char的函数体zap-to-char函数体包含kill当前光标位置至指定字符之间文本的代码。代码的第一部分如下: (kill-region (point) ...(point)是光标的当前位置 代码的下一个部分是一个progn语句。progn的body部分由search-forward和point组成。 在学习完search-forward后,很容易懂progn。 search-forward函数search-forward函数被用于定位字符(zapped-for-character)。如果查找成功, search-forward会将point设置在要查找的目标字符串的最后一个字符的后面。(zap-to-char中目标字符串只有一个字符)如果是 向后查找,则search-forward会将point设置在查找目标字符串第一个字符的前面。查找成功后,search-forward将返回t。 在zap-to-char中,search-forward函数部分如下: (search-forward (char-to-string char) nil nil arg)search-forward函数包含四个参数:
传递给zap-to-char是一个字符。Lisp解释器对字符串和字符的处理是不同的。因为search-forward函数查询的是一个字符 串,传递给zap-to-char函数接收到的是一个字符,因此参数必须被转换为字符串,否则search-forward将报错。char-to- string用于处理这种转换。
使用search-forward语句的模板: (search-forward "target-string" prognprogn是一个特殊的form。它使传递给它的参数依次被执行,并返回最后一个值。前面部分只是被执行,它们的返回值被丢弃。 progn语句的模板: (prognzap-to-char中的progn语句做了两件事:将point设置到正确的位置;返回point的位置以便kill-region知道要操作的范围。 progn的第一个参数是search-forward。当search-forward找到了字符串,它会将point设置在查找目标字符串的最 后一个字符的后面。(这里目标字符串只有一个字符长)如果是向后查找,search-forward会将poing设置在查找目标的第一个字符的前面。 point的移动是side effect(单方面的,不影响界面)。 progn的第二个参数是表达式(point)。这个表达式返回point的值,即search-forward设置的那个值。这个值被作为progn语句的返回值将作为kill-region的第二个参数传递给kill-region函数。 zap-to-char的总结前面了解了search-forward和progn是如何工作的,我们可以看到整个zap-to-char函数是如何工作的。 kill-region的第一个参数是执行zap-to-char命令时的光标位置。在progn的内部,查找函数将poing移动到要查找目标 (zapped-to-character)的后面。kill-region函数将这两个point中的第一个作为操作区域(region)的开始位置, 第二个参数作为结束位置,然后移除这个区域。 progn是必需的,因为kill-region命令需要两个参数;如果把search-forward和point语句直接作为kill-region的参数将报错。progn语句是一个单独的参数,它的返回值将作为传递给kill-region的第二个参数。 kill-regionzap-to-char函数使用了kill-region函数。函数将从一个region中clip文本到kill ring中。 在Emacs 21中这个函数使用了condition-case和copy-region-as-kill,这两个函数都将在后面解释,confition-case是一个特别重要的form。 实际上,kill-region函数调用了condition-case,它需要3个参数。第一个参数不做什么,第二个参数包含了正常工作时需要执行的代码。第三个参数包含了出错时需要执行的代码。 完整的kill-region定义下面将介绍condition-case。首先来看kill-region的完整定义: (defun kill-region (beg end) condition-case前面说过,当Emacs Lisp解释器在执行语句发生错误时,它将提供帮助信息,这被称为"signaling a error"。通常,程序将停止执行并显示错误信息。 然而在一些复杂的情况下。程序不应该在出错的时候只是简单的停止程序执行。在kill-region函数中,一个典型的错误是,如果在只读缓冲区中 删除文本时,文本将不会被删除。因此kill-region函数包含了处理这种情况的代码。这些代码在kill-region函数中condition- case语句的内部。 condition-case的模板如下: (condition-case如果没有发生错误,解释器将执行bodyform语句。 错误发生时,函数将产生错误信息,定义一个或者多个错误条件名称(condition name)。 condition-case的第三个参数是一个错误处理器。一个错误处理器包含了两个部分,一个condition-name和一个body。如 果错误处理器的condition-name与发生错误时的condition-name匹配,错误处理器的body部分将执行。 错误处理器中的错误条件名称(condition-name)可以是一个单一的condition name也可以是包含多个condition name的list。 condition-case语句可以包含一个或多个错误处理器。当错误发生时,第一个被匹配的处理器被执行。 最后,condition-case语句的第一个参数var,有时被绑定到包含错误信息的变量上。如果它为nil,比如在kill-region中,错误消息将被丢弃。 简单来说,在kill-region函数中,condition-case的工作如下: If no errors, run only this code delete-and-extract-region一个condition-case语句有二个部分,一个是正常时执行的,但它有可能会产生错误。另一个部分用于出错时执行。 先来看kill-region中正常运行的代码: (let ((string (delete-and-extract-region beg end)))看起来比较复杂,使用了新的函数:delete-and-extract-region,kill-append和kill-new,和新的变量last-command和this-command。 delete-and-extract-region函数是一个内置函数,它删除region中的文本并返回这些文本。这个函数实际上是移除(removes)文本。(当不能移除时,它给出错误信号) 这里的let语句将delete-and-extract-region的返回值赋给局部变量string中。这也就是从缓冲区中删除的文本。 如果变量string指向了文本,那些文本就被添加到kill ring,如果变量值为nil则表示没有文本被删除。 这里使用了when来检查变量string是否指向了文本块。when语句是程序员的一种简便写法。when语句是没有else部分的if语句。可以把when理解为if。 技术上来说,when是一个Lisp宏。Lisp宏允许你定义新的控制结构和其它语言功能。它告诉解释器如何计算另一个Lisp语句的值,并返回计算的结果。这里的'另一个表达式'就是一个if表达式。C语言里也提供了宏。但这是不同的,但它们同样很有用。 如果string变量有内容,另一个条件表达式被执行。这是一个包含了then部分和else部分的if语句。 (if (eq last-command 'kill-region) 如果前一个命令是kill-region,then部分被执行。如果不是,else部分将被执行。 last-command是一个Emacs变量。通常,当一个函数被执行,Emacs将设置last-command的值为前一个命令。 在这段定义中,if语句检查前一个命令是否为kill-region。 (kill-append string (< end beg))连续拷贝新文本到kill ring中前一个clipped的文本块中。如果(< end beg)表达式为true,kill-append添加文本到前一个被clipped的文本块中。 如果yank文本,比如'粘贴',将一次得到整个文本块。用这种方式,你可以删除一行中的两个单词,然后使用一次yank操作,重新得到这两个单词,((< end beg)语句保持单词的顺序是正确的) 如果前一个命令不是kill-region,kill-new函数将被执行,它将文本作为kill ring中的最后一个元素添加进去,然后将变量kill-ring-yank-pointer设置到上面。 delete-and-extract-regionzap-to-char命令使用了delete-and-extract-region函数,它使用了另外两个函数, copy-region-as-kill和del_range_1。copy-region-as-kill函数将在下节讨论;它复制了region的一 份拷贝到kill ring中,因此内容可以yanked回来。 delete-and-extract-region函数移除region中的内容且不能恢复。 与其它代码不同,delete-and-extract-region不是用Emacs Lisp编写的;它是用C编写的,这也是Emacs的一个基础系统。 与其它Emacs原生函数一样,delete-and-extract=region是C宏,宏是一个代码模板。完整宏如下: DEFUN ("delete-and-extract-region", Fdelete_and_extract_region,DEFUN与Lisp中的defun是同样的用途。DEFUN后面括号中有七个部分:
因此,goto-char的文档字符串的前两行如下: "Set point to POSITION, a number or marker.\n\ 在C宏中,紧接在后面是正式的参数,和参数类型语句,接下来就是宏的'body'部分。delete-and-extract-region的'body'包含了两行: validate_region (&start, &end);第一个函数validate_region检查传递的区域起始位置和结束位置是否是在规定的范围内,检查参数类型是否正确。第二个函数del_range_1,执行删除文本的操作。 del_range_1是一个复杂的函数我们不深入研究。它修改缓冲区并执行其它操作。 传递给del_range的两个参数XINT (start) and XINT (end)值得研究一下。 C语言中,start和end是标记了被删除区域的开始位置和结束位置的两个整数。 早期版本的Emacs中,这两个数字是32bits长,但这个代码运行比较慢。三个bit被用于指定类型信息,四个bit被用于处理内存;其它bits被作为'content'。 XINT是一个C宏它从bits集合中解析出相关的数字;4个bits被丢弃。 delete-and-extract-region命令看起来如下: del_range_1 (XINT (start), XINT (end), 1, 1); 它删除start和end之间的region。 从这点来看Emacs Lisp很简单;它隐藏了大量复杂的工作。 使用用defvar初始化变量与delete-and-extract-region函数不同,copy-region-as-kill函数是用 Emacs Lisp编写的。它内部有两个函数kill-append和kill-new,复制缓冲区区域中的信息到变量kill-ring中。这节讨论kill- ring变量是如何被defvar创建和初始化的。 在Emacs Lisp中kill-ring之类的变量是用defvar创建和初始化的。这个名称来源于"define variable"。 defvar与setq设置变量类似。与setq不同的两点:第一,它只给未赋值的变量赋值,如果变量已经有值,defvar将不会覆盖已经存在的值。第二,defvar有一个文档字符串。 (另一个特别的form是defcustom,被设计为可以让用户自定义。它比defvar有更多的功能。) 查看变量的当前值可以使用describe-variable函数查看任何变量的当前值,通常可以用C-h v来调用。比如可以C-h v然后输入kill-ring将看到 当前kill ring的值,同时也能看到kill-ring的文档字符串: Documentation:kill ring是使用defvar按下面的方法定义的: (defvar kill-ring nil这个变量定义中,变量初始化为nil。这意味着如果没有保存任何东西,使用yank时将不会获取到任何信息。文档字符串的写法与使用defun时的文档字 符串是一样的,文档字符串的第一行必须是一个完整的语句,因为一些命令,比如apropos只打印文档字符串的第一行。后面的行不应该使用缩进;否则如果 用C-h v(describe-variable)查看时将会混乱。 defvar时使用星号以前,Emacs使用defvar来定义希望被用户修改的变量和不希望被用户修改的变量。尽管你可以用defvar定义自定义变量,但是请使用defcustom来代替。 当使用defvar设定变量时,可以在文档字符串的第一个位置添加*号来来区分变量是否为可以设值的变量。比如: (defvar shell-command-default-error-buffer nil这表示你可以使用edit-options命令临时修改shell-command-default-error-buffer的值。 edit-options设置的值只在当前编辑会话中有用。新值并不会被保存。每次Emacs启动时它将读取原始值,除非你在.emacs文件中设定它。 copy-region-as-kill这个函数从缓冲区中复制区域中的内容(使用kill-append或kill-new)并保存到kill-ring上。 如果在调用kill-region后立即调用copy-region-as-kill,Emacs会将新的文本追加到前一个复制的文本中。这意味着 你使用yank时将得前面两次操作的所有文本。另一方面,如果在copy-region-as-kill之前执行了一些命令,则函数复制的文本块将不会放 在一起。 完整的copy-region-as-kill函数定义下面是Emacs 21中copy-region-as-kill函数定义: (defun copy-region-as-kill (beg end) 这个函数也可以拆分成多个部分: (defun copy-region-as-kill (argument-list) 参数是beg、end和参数为"r"的交互式函数,因此这两个参数将指向region的开始位置和结束位置。 一旦设置了一个mark,缓冲区就总会包含一个region。可以使用Transient Mark模式来高亮显示region。(没人会希望region一直处理于高亮状态,因此Transient Mark模式下只会在适当的时候才会高亮显示。许多人都关掉了Transient Mark模式,因此region从不会高亮显示) copy-region-as-kill函数体是一个以if开头的子句。这个子句区分了两种情况:这个命令的前一个命令是否是kill- region命令。第一种情况,新的region被追加到前一个被复制的文本块中。否则,它将插入一个新的文本块到kill ring中。 copy-region-as-kill的body部分copy-region-as-kill函数和kill-function的工作很相似。两者都是为了将同一行中的两次或多次kill操作合并到同一个块中。如果用yank回来,将一次获得所有的文本块。并且,不管是向前删除还是向后删除,文本块都保持了正确的位置。 与kill-region相同,copy-region-as-kill函数也使用了last-command(它保持了对次Emacs命令调用的跟踪)变量。 last-command和this-command通常,任何一个函数被执行,Emacs将在函数被挪时设置this-command为被执行的函数。同时,Emacs将last-command的值设置为this-command的前一个值。 在copy-region-as-kill函数的body部分,一个if语句检查了last-command的值是否为kill-region。如 果是,则if语句被执行;它使用kill-append函数将本次函数调用复制的文本合并到kill ring的第一个元素(CAR)中。如果last-command不为kill-region,则copy-region-as-kill函数将使用 kill-new函数在kill ring中添加一个新的元素。 这个if语句如下,它使用了eq函数: (if (eq last-command 'kill-region)eq函数测试它的第二个参数与第一个参数是否为相同的Lisp对象。eq函数与用于测试相等的equal函数类似,不同之处在于:eq测试两个对象是否为指向同一个对象,而equal则检查两个参数的结构和同容是否相同。 如果前一个命令是kill-region,则Emacs Lisp解释器将调用kill-append函数。 kill-append函数kill-append函数如下: (defun kill-append (string before-p)kill-append函数使用了kill-new函数。 首先来看传递给kill-new的参数。它使用了concat连接新文本和kill ring的CAR。是合并到CAR元素的前面还是合并到CAR元素后面取决于if语句: (if before-p ; if-part如果被kill的region位于前一个命令kill的region的前面,那么它将被合并到前一次删除的资源的前面,如果被删除的文本在前次删除文本的后面,那它将被合并到前次删除资源的后面。if语句使用before-p决定如何放置。 符号before-p是kill-append的参数。当kill-append被执行时,它被绑定到实际参数计算出来的值上。在这里是表达式 (< end beg)。这个表达式并不能直接决定被删除的文本应该放在上个命令删除的文本的前面还是后面,它决定的是end是否小于beg。意味着用户是向前删除还是 向后删除。如果(< end beg)则文本应该加有前一次文本的前面,否则文本应该加在前次文本的后面。 新文本加到前面时,执行: (concat string (car kill-ring))新文本加到后面时,执行: (concat (car kill-ring) string))我们可以意识到kill-append修改了kill ring。kill ring是一个list,它的每个元素保存了文本。kill-append函数使用kill-new函数,kill-new函数使用了setcar函数。 kill-new函数(defun kill-new (string &optional replace) 先看下面的部分: (if (and replace kill-ring) 条件测试(and replace kill-ring),如果两个kill ring中有内容,并且replace变量为true则返回true。 kill-append函数将replace设置为true;然后当kill ring至少有一个元素时,setcar语句被执行: (setcar kill-ring string)setcar函数将kill-ring的第一个元素修改为string的值。它替换了原来的元素。 如果kill ring为空,或者replace为false,则条件语句的else部分将执行: (setq kill-ring (cons string kill-ring))语句先通过在原来的kill ring前添加新元素string,而构造了一个新的kill ring。然后执行了第二个if子句。第二个if子名防止了kill ring增长过大。 依次来看这两个语句。 setq的这行将string添加到旧的kill ring组成的新list重新设置给kill-ring。 第二个if子名,防止了kill ring增长得过长。 (if (> (length kill-ring) kill-ring-max)这段代码检查kill ring的长度是否已经超过了允许的最大长度——kill-ring-max(默认为60)。如果kill ring过长,则将kill ring的最后一个元素设置为nil。执行这个操作使用了两个函数:nthcdr和setcdr。 setcdr设置list的CDR部分,setcar设置list的CAR部分。在这里,setcdr不会设置kill ring的CDR部分;nthcdr函数限制了设置CDR的位置。 例: (setq trees '(maple oak pine birch))setcdr返回值为nil,是因为它设置的CDR是nil。 kill-new函数中的下一行语句是: (setq kill-ring-yank-pointer kill-ring)kill-ring-yank-pointer也是一个全局变量,它被设置为kill-ring。 尽管kill-ring-yank-pointer被称为pointer,实际上却是kill ring变量。但选用名字是为了帮助人们懂得这个变量起的作用。这个变量用于yank和yank-pop等函数。 现在,回到函数的最前面的两行: (and (fboundp 'menu-bar-update-yank-menu)这个语句第一个元素是函数and。 and将依次对每个参数求值只到某个参数返回值为nil,这种情况下and语句将返回nil;如果没有参数返回值为nil,返回值将是最后一个参数 的值。(这种情况下返回值不会为nil,在Emacs Lisp里可以作为true)。换言之,and语句只有在所有参数都返回true的情况下才返回true。 在这里,语句测试了menu-bar-update-yank-menu是否是一个函数,如果是则调用它。如果测试的参数符号是一个函数定义而不是'is not void',则fboundp返回true,如果函数未定义则我们将得到错误信息。 这个and和if语句效果如下: if the-menu-bar-function-exists menu-bar-update-yank-menu函数允许用户使用'Select and Paste'菜单操作,并且可以在菜单上看到文本块。 最后一个语句kill-new函数添加新的文本到窗口系统中,以便在不同的程序中进行复制粘贴操作。比如:在XWindow系统中x-select-text函数将文本存储在X系统操作的内存中,你可以在另一个程序中粘贴。 语句结构如下: (if interprogram-cut-function 如果interprogram-cut-function存在,则Emacs执行funcall,它将第一个参数作为函数,并将其它参数传递给这个函数。 回顾
car返回list的第一个元素;cdr返回list中从第二个元素开始的list。 例: (car '(1 2 3 4 5 6 7))
cons将第一个参数添加到第二个参数前面。 例: (cons 1 '(2 3 4))
返回对list求'n'次CDR的值。 例: (nthcdr 3 '(1 2 3 4 5 6 7))
setcar修改list中的第一个元素;setcdr修改list中第二个元素开始的list。 例: (setq triple '(1 2 3))
依次执行各个参数并返回最后一个参数的值。 例: (progn 1 2 3 4)
记录当前缓冲区的任何narrowing,在执行完它的参数后,恢复narrowing。
查找字符串,如果找到则将point设置到那个位置。 它接收4个参数:
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 3939 次