while
loopThe second part of the body of the let*
deals with forward
motion. It is a while
loop that repeats itself so long as the
value of arg
is greater than zero. In the most common use of
the function, the value of the argument is 1, so the body of the
while
loop is evaluated exactly once, and the cursor moves
forward one paragraph.
This part handles three situations: when point is between paragraphs, when there is a fill prefix and when there is no fill prefix.
The while
loop looks like this:
;; going forwards and not at the end of the buffer (while (and (> arg 0) (not (eobp))) ;; between paragraphs ;; Move forward over separator lines... (while (and (not (eobp)) (progn (move-to-left-margin) (not (eobp))) (looking-at parsep)) (forward-line 1)) ;; This decrements the loop (unless (eobp) (setq arg (1- arg))) ;; ... and one more line. (forward-line 1)
(if fill-prefix-regexp ;; There is a fill prefix; it overrides parstart; ;; we go forward line by line (while (and (not (eobp)) (progn (move-to-left-margin) (not (eobp))) (not (looking-at parsep)) (looking-at fill-prefix-regexp)) (forward-line 1))
;; There is no fill prefix; ;; we go forward character by character (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))
;; and if there is no fill prefix and if we are not at the end, ;; go to whatever was found in the regular expression search ;; for sp-parstart (if (< (point) (point-max)) (goto-char start))))
We can see that this is a decrementing counter while
loop,
using the expression (setq arg (1- arg))
as the decrementer.
That expression is not far from the while
, but is hidden in
another Lisp macro, an unless
macro. Unless we are at the end
of the buffer—that is what the eobp
function determines; it
is an abbreviation of ‘End Of Buffer P’—we decrease the value
of arg
by one.
(If we are at the end of the buffer, we cannot go forward any more and
the next loop of the while
expression will test false since the
test is an and
with (not (eobp))
. The not
function means exactly as you expect; it is another name for
null
, a function that returns true when its argument is false.)
Interestingly, the loop count is not decremented until we leave the space between paragraphs, unless we come to the end of buffer or stop seeing the local value of the paragraph separator.
That second while
also has a (move-to-left-margin)
expression. The function is self-explanatory. It is inside a
progn
expression and not the last element of its body, so it is
only invoked for its side effect, which is to move point to the left
margin of the current line.
The looking-at
function is also self-explanatory; it returns
true if the text after point matches the regular expression given as
its argument.
The rest of the body of the loop looks difficult at first, but makes sense as you come to understand it.
First consider what happens if there is a fill prefix:
(if fill-prefix-regexp ;; There is a fill prefix; it overrides parstart; ;; we go forward line by line (while (and (not (eobp)) (progn (move-to-left-margin) (not (eobp))) (not (looking-at parsep)) (looking-at fill-prefix-regexp)) (forward-line 1))
This expression moves point forward line by line so long as four conditions are true:
The last condition may be puzzling, until you remember that point was
moved to the beginning of the line early in the forward-paragraph
function. This means that if the text has a fill prefix, the
looking-at
function will see it.
Consider what happens when there is no fill prefix.
(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))
This while
loop has us searching forward for
sp-parstart
, which is the combination of possible whitespace
with the local value of the start of a paragraph or of a paragraph
separator. (The latter two are within an expression starting
\(?:
so that they are not referenced by the
match-beginning
function.)
The two expressions,
(setq start (match-beginning 0)) (goto-char start)
mean go to the start of the text matched by the regular expression search.
The (match-beginning 0)
expression is new. It returns a number
specifying the location of the start of the text that was matched by
the last search.
The match-beginning
function is used here because of a
characteristic of a forward search: a successful forward search,
regardless of whether it is a plain search or a regular expression
search, moves point to the end of the text that is found. In this
case, a successful search moves point to the end of the pattern for
sp-parstart
.
However, we want to put point at the end of the current paragraph, not
somewhere else. Indeed, since the search possibly includes the
paragraph separator, point may end up at the beginning of the next one
unless we use an expression that includes match-beginning
.
When given an argument of 0, match-beginning
returns the
position that is the start of the text matched by the most recent
search. In this case, the most recent search looks for
sp-parstart
. The (match-beginning 0)
expression returns
the beginning position of that pattern, rather than the end position
of that pattern.
(Incidentally, when passed a positive number as an argument, the
match-beginning
function returns the location of point at that
parenthesized expression in the last search unless that parenthesized
expression begins with \(?:
. I don’t know why \(?:
appears here since the argument is 0.)
The last expression when there is no fill prefix is
(if (< (point) (point-max)) (goto-char start))))
(Note that this code snippet is copied verbatim from the original code,
so the two extra ending parentheses are matching the previous if
and while
.)
This says that if there is no fill prefix and if we are not at the
end, point should move to the beginning of whatever was found by the
regular expression search for sp-parstart
.
The full definition for the forward-paragraph
function not only
includes code for going forwards, but also code for going backwards.
If you are reading this inside of GNU Emacs and you want to see the
whole function, you can type C-h f (describe-function
)
and the name of the function. This gives you the function
documentation and the name of the library containing the function’s
source. Place point over the name of the library and press the RET
key; you will be taken directly to the source. (Be sure to install
your sources! Without them, you are like a person who tries to drive
a car with his eyes shut!)