while
循环let*
的第二部分处理前进动作。它是一个 while
循环,只要 arg
的值大于零就重复执行。在函数的最常见用法中,参数的值为 1,因此 while
循环的主体只被评估一次,光标向前移动一个段落。
这部分处理三种情况:当光标在段落之间时,当存在填充前缀时,当不存在填充前缀时。
while
循环如下:
;; 向前移动且不在缓冲区末尾 (while (and (> arg 0) (not (eobp))) ;; 在段落之间 ;; 向前移动过分隔行… (while (and (not (eobp)) (progn (move-to-left-margin) (not (eobp))) (looking-at parsep)) (forward-line 1)) ;; 这减少了循环计数 (unless (eobp) (setq arg (1- arg))) ;; …再多移动一行。 (forward-line 1)
(if fill-prefix-regexp ;; 存在填充前缀;它覆盖了 parstart; ;; 我们逐行向前移动 (while (and (not (eobp)) (progn (move-to-left-margin) (not (eobp))) (not (looking-at parsep)) (looking-at fill-prefix-regexp)) (forward-line 1))
;; 不存在填充前缀; ;; 我们逐字符向前移动 (while (and (re-search-forward sp-parstart nil 1) (progn (setq start (match-beginning 0)) (goto-char start) (not (eobp))) (progn (move-to-left-margin) (not (looking-at parsep))) (or (not (looking-at parstart)) (and use-hard-newlines (not (get-text-property (1- start) 'hard))))) (forward-char 1))
;; 如果没有填充前缀且不在末尾, ;; 则转到在正则表达式搜索 sp-parstart 中找到的内容 (if (< (point) (point-max)) (goto-char start))))
我们可以看到这是一个递减计数器的 while
循环,使用表达式 (setq arg (1- arg))
作为递减器。该表达式并不远离 while
,但被隐藏在另一个 Lisp 宏中,即 unless
宏。除非我们在缓冲区末尾——这是 eobp
函数确定的;它是 ‘End Of Buffer P’ 的缩写——否则我们会将 arg
的值减小一。
(如果我们在缓冲区末尾,我们无法再向前移动,由于测试是与 (not (eobp))
的 and
,下一个循环的表达式将测试为假,因为 not
函数的作用与您期望的完全相同;它是 null
的另一个名称,当其参数为假时返回真。)
有趣的是,循环计数器在我们离开段落之间之前并未递减,除非我们到达缓冲区的末尾或停止看到段落分隔符的本地值。
这第二个 while
也有一个 (move-to-left-margin)
表达式。该函数是不言自明的。它在一个 progn
表达式中,并且不是其主体的最后一个元素,因此它仅被调用以产生其移动到当前行左边缘的副作用。
looking-at
函数同样不言自明;如果点之后的文本与其参数给定的正则表达式匹配,则返回真。
在理解的过程中,循环体的其余部分起初看起来可能有些困难,但随着理解,它会变得合理。
首先考虑如果存在填充前缀时会发生什么:
(if fill-prefix-regexp ;; 存在填充前缀;它覆盖了 parstart; ;; 我们逐行向前移动 (while (and (not (eobp)) (progn (move-to-left-margin) (not (eobp))) (not (looking-at parsep)) (looking-at fill-prefix-regexp)) (forward-line 1))
这个表达式会使点逐行向前移动,只要四个条件都为真:
最后一个条件可能令人困惑,直到您记住点在 forward-paragraph
函数中早期被移动到行的开头。这意味着如果文本有填充前缀,looking-at
函数将会看到它。
考虑当没有填充前缀时会发生什么。
(while (and (re-search-forward sp-parstart nil 1) (progn (setq start (match-beginning 0)) (goto-char start) (not (eobp))) (progn (move-to-left-margin) (not (looking-at parsep))) (or (not (looking-at parstart)) (and use-hard-newlines (not (get-text-property (1- start) 'hard))))) (forward-char 1))
这个 while
循环让我们向前搜索 sp-parstart
,它是可能的空白字符与段落或段落分隔符的本地值的组合。 (后两者都在以 \(?:
开头的表达式中,以便它们不被 match-beginning
函数引用。)
这两个表达式,
(setq start (match-beginning 0)) (goto-char start)
意味着转到由正则表达式搜索匹配的文本的开头。
(match-beginning 0)
表达式是新的。它返回一个指定由最近的搜索匹配的文本的开头位置的数字。
当给定参数 0 时,match-beginning
返回最近搜索匹配的文本的开头位置。在这种情况下,最近的搜索寻找 sp-parstart
。(match-beginning 0)
表达式返回该模式的开头位置,而不是该模式的结束位置。
(顺便说一下,当作为参数传递一个正数时,match-beginning
函数返回最后一次搜索中括号表达式的位置,除非该括号表达式以 \(?:
开头。我不知道为什么这里出现 \(?:
,因为参数是 0。)
当没有填充前缀时的最后一个表达式是
(if (< (point) (point-max)) (goto-char start))))
这表示如果没有填充前缀且不在末尾,点应该移动到由正则表达式搜索 sp-parstart
找到的内容的开头。
forward-paragraph
函数的完整定义不仅包括前进的代码,还包括后退的代码。
如果您在 GNU Emacs 中阅读此内容并想查看整个函数,请键入 C-h f(describe-function
)和函数的名称。这会给您函数文档和包含函数源代码的库的名称。将点放在库的名称上,然后按 RET 键;您将直接转到源代码。(确保安装了源代码!没有源代码,您就像闭着眼睛开车的人一样!)