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 键;您将直接转到源代码。(确保安装了源代码!没有源代码,您就像闭着眼睛开车的人一样!)