paragraphs.el 18 KB
Newer Older
1
;;; paragraphs.el --- paragraph and sentence parsing
Eric S. Raymond's avatar
Eric S. Raymond committed
2

3
;; Copyright (C) 1985, 86, 87, 91, 94, 95, 96, 1997, 1999, 2000, 2001
Richard M. Stallman's avatar
Richard M. Stallman committed
4
;;    Free Software Foundation, Inc.
Eric S. Raymond's avatar
Eric S. Raymond committed
5

Eric S. Raymond's avatar
Eric S. Raymond committed
6
;; Maintainer: FSF
Eric S. Raymond's avatar
Eric S. Raymond committed
7
;; Keywords: wp
Eric S. Raymond's avatar
Eric S. Raymond committed
8

Joseph Arceneaux's avatar
Joseph Arceneaux committed
9 10 11 12
;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
Roland McGrath's avatar
Roland McGrath committed
13
;; the Free Software Foundation; either version 2, or (at your option)
Joseph Arceneaux's avatar
Joseph Arceneaux committed
14 15 16 17 18 19 20 21
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
Erik Naggum's avatar
Erik Naggum committed
22 23 24
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
Joseph Arceneaux's avatar
Joseph Arceneaux committed
25

26 27 28 29 30
;;; Commentary:

;; This package provides the paragraph-oriented commands documented in the
;; Emacs manual.

Eric S. Raymond's avatar
Eric S. Raymond committed
31
;;; Code:
Joseph Arceneaux's avatar
Joseph Arceneaux committed
32

Richard M. Stallman's avatar
Richard M. Stallman committed
33 34 35 36
(defgroup paragraphs nil
  "Paragraph and sentence parsing."
  :group 'editing)

37
(define-minor-mode use-hard-newlines
38 39 40
  "Minor mode to distinguish hard and soft newlines.
When active, the functions `newline' and `open-line' add the
text-property `hard' to newlines that they insert, and a line is
41
only considered as a candidate to match `paragraph-start' or
42
`paragraph-separate' if it follows a hard newline.
43

44 45
Prefix argument says to turn mode on if positive, off if negative.
When the mode is turned on, if there are newlines in the buffer but no hard
46
newlines, ask the user whether to mark as hard any newlines preceeding a
47 48
`paragraph-start' line.  From a program, second arg INSERT specifies whether
to do this; it can be `never' to change nothing, t or `always' to force
49
marking, `guess' to try to do the right thing with no questions, nil
50 51 52 53
or anything else to ask the user.

Newlines not marked hard are called \"soft\", and are always internal
to paragraphs.  The fill functions insert and delete only soft newlines."
Markus Rost's avatar
Markus Rost committed
54
  :group 'paragraphs
55 56
  :extra-args (insert)
  (when use-hard-newlines
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
    ;; Turn mode on
    ;; Intuit hard newlines --
    ;;   mark as hard any newlines preceding a paragraph-start line.
    (if (or (eq insert t) (eq insert 'always)
	    (and (not (eq 'never insert))
		 (not (text-property-any (point-min) (point-max) 'hard t))
		 (save-excursion
		   (goto-char (point-min))
		   (search-forward "\n" nil t))
		 (or (eq insert 'guess)
		     (y-or-n-p "Make newlines between paragraphs hard? "))))
	(save-excursion
	  (goto-char (point-min))
	  (while (search-forward "\n" nil t)
	    (let ((pos (point)))
	      (move-to-left-margin)
73 74 75 76 77 78 79 80
	      (when (looking-at paragraph-start)
		(set-hard-newline-properties (1- pos) pos))
	      ;; If paragraph-separate, newline after it is hard too.
	      (when (looking-at paragraph-separate)
		(set-hard-newline-properties (1- pos) pos)
		(end-of-line)
		(unless (eobp)
		  (set-hard-newline-properties (point) (1+ (point)))))))))))
81

82
(defcustom paragraph-start "\f\\|[ \t]*$" "\
83 84 85 86
*Regexp for beginning of a line that starts OR separates paragraphs.
This regexp should match lines that separate paragraphs
and should also match lines that start a paragraph
\(and are part of that paragraph).
87

88 89 90 91 92
This is matched against the text at the left margin, which is not necessarily
the beginning of the line, so it should never use \"^\" as an anchor.  This
ensures that the paragraph functions will work equally well within a region
of text indented by a margin setting.

93
The variable `paragraph-separate' specifies how to distinguish
94 95
lines that start paragraphs from lines that separate them.

Stefan Monnier's avatar
Typo.  
Stefan Monnier committed
96
If the variable `use-hard-newlines' is non-nil, then only lines following a
Richard M. Stallman's avatar
Richard M. Stallman committed
97 98 99
hard newline are considered to match."
  :group 'paragraphs
  :type 'regexp)
Jim Blandy's avatar
Jim Blandy committed
100

101 102 103 104 105 106
;; paragraph-start requires a hard newline, but paragraph-separate does not:
;; It is assumed that paragraph-separate is distinctive enough to be believed
;; whenever it occurs, while it is reasonable to set paragraph-start to
;; something very minimal, even including "." (which makes every hard newline
;; start a new paragraph).

Richard M. Stallman's avatar
Richard M. Stallman committed
107 108
(defcustom paragraph-separate "[ \t\f]*$"
  "*Regexp for beginning of a line that separates paragraphs.
109
If you change this, you may have to change `paragraph-start' also.
110

111 112 113
This is matched against the text at the left margin, which is not necessarily
the beginning of the line, so it should not use \"^\" as an anchor.  This
ensures that the paragraph functions will work equally within a region of
Richard M. Stallman's avatar
Richard M. Stallman committed
114 115 116
text indented by a margin setting."
  :group 'paragraphs
  :type 'regexp)
Jim Blandy's avatar
Jim Blandy committed
117

118 119 120 121 122
(defcustom sentence-end-double-space t
  "*Non-nil means a single space does not end a sentence.
This is relevant for filling.  See also `sentence-end-without-period'
and `colon-double-space'.

123 124 125
This value is used by the function `sentence-end' to construct the
regexp describing the end of a sentence, in case when the value of
the variable `sentence-end' is nil.  See Info node `Sentences'."
126 127 128 129 130 131
  :type 'boolean
  :group 'fill)

(defcustom sentence-end-without-period nil
  "*Non-nil means a sentence will end without a period.
For example, a sentence in Thai text ends with double space but
132 133 134 135 136
without a period.

This value is used by the function `sentence-end' to construct the
regexp describing the end of a sentence, in case when the value of
the variable `sentence-end' is nil.  See Info node `Sentences'."
137 138 139
  :type 'boolean
  :group 'fill)

140 141 142
(defcustom sentence-end-without-space
  "$B!#!%!)!*$A!##.#?#!$(0!$!%!)!*$(G!$!%!)!*(B"
  "*String containing characters that end sentence without following spaces.
143 144 145 146

This value is used by the function `sentence-end' to construct the
regexp describing the end of a sentence, in case when the value of
the variable `sentence-end' is nil.  See Info node `Sentences'."
147 148 149
  :group 'paragraphs
  :type 'string)

150
(defcustom sentence-end nil
Richard M. Stallman's avatar
Richard M. Stallman committed
151
  "*Regexp describing the end of a sentence.
Eli Zaretskii's avatar
Eli Zaretskii committed
152
The value includes the whitespace following the sentence.
153 154
All paragraph boundaries also end sentences, regardless.

155 156 157
The value nil means to use the default value defined by the
function `sentence-end'.  You should always use this function
to obtain the value of this variable."
Richard M. Stallman's avatar
Richard M. Stallman committed
158
  :group 'paragraphs
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
  :type '(choice regexp (const :tag "Use default value" nil)))

(defun sentence-end ()
  "Return the regexp describing the end of a sentence.

This function returns either the value of the variable `sentence-end'
if it is non-nil, or the default value constructed from the
variables `sentence-end-double-space', `sentence-end-without-period'
and `sentence-end-without-space'.  The default value specifies
that in order to be recognized as the end of a sentence, the
ending period, question mark, or exclamation point must be
followed by two spaces, unless it's inside some sort of quotes or
parenthesis.  See Info node `Sentences'."
  (or sentence-end
      (concat (if sentence-end-without-period "\\w  \\|")
174
              "\\([.?!][]\"'\xd0c9\x5397d)}]*"
175 176 177 178
              (if sentence-end-double-space
                  "\\($\\| $\\|\t\\|  \\)" "\\($\\|[\t ]\\)")
              "\\|[" sentence-end-without-space "]+\\)"
              "[ \t\n]*")))
Richard M. Stallman's avatar
Richard M. Stallman committed
179 180 181 182 183 184 185 186 187 188 189

(defcustom page-delimiter "^\014"
  "*Regexp describing line-beginnings that separate pages."
  :group 'paragraphs
  :type 'regexp)

(defcustom paragraph-ignore-fill-prefix nil
  "*Non-nil means the paragraph commands are not affected by `fill-prefix'.
This is desirable in modes where blank lines are the paragraph delimiters."
  :group 'paragraphs
  :type 'boolean)
Roland McGrath's avatar
Roland McGrath committed
190

Joseph Arceneaux's avatar
Joseph Arceneaux committed
191 192
(defun forward-paragraph (&optional arg)
  "Move forward to end of paragraph.
193 194
With argument ARG, do it ARG times;
a negative argument ARG = -N means move backward N paragraphs.
Joseph Arceneaux's avatar
Joseph Arceneaux committed
195 196 197 198

A line which `paragraph-start' matches either separates paragraphs
\(if `paragraph-separate' matches it also) or is the first line of a paragraph.
A paragraph end is the beginning of a line which is not part of the paragraph
199 200
to which the end of the previous line belongs, or the end of the buffer.
Returns the count of paragraphs left to move."
Joseph Arceneaux's avatar
Joseph Arceneaux committed
201 202
  (interactive "p")
  (or arg (setq arg 1))
203 204
  (let* ((opoint (point))
	 (fill-prefix-regexp
Joseph Arceneaux's avatar
Joseph Arceneaux committed
205 206 207
	  (and fill-prefix (not (equal fill-prefix ""))
	       (not paragraph-ignore-fill-prefix)
	       (regexp-quote fill-prefix)))
208 209 210 211 212
	 ;; Remove ^ from paragraph-start and paragraph-sep if they are there.
	 ;; These regexps shouldn't be anchored, because we look for them
	 ;; starting at the left-margin.  This allows paragraph commands to
	 ;; work normally with indented text.
	 ;; This hack will not find problem cases like "whatever\\|^something".
213 214 215 216 217 218 219 220 221
	 (parstart (if (and (not (equal "" paragraph-start))
			    (equal ?^ (aref paragraph-start 0)))
		       (substring paragraph-start 1)
		     paragraph-start))
	 (parsep (if (and (not (equal "" paragraph-separate))
			  (equal ?^ (aref paragraph-separate 0)))
		     (substring paragraph-separate 1)
		   paragraph-separate))
	 (parsep
Joseph Arceneaux's avatar
Joseph Arceneaux committed
222
	  (if fill-prefix-regexp
223
	      (concat parsep "\\|"
Joseph Arceneaux's avatar
Joseph Arceneaux committed
224
		      fill-prefix-regexp "[ \t]*$")
225
	    parsep))
226
	 ;; This is used for searching.
227
	 (sp-parstart (concat "^[ \t]*\\(?:" parstart "\\|" parsep "\\)"))
228
	 start found-start)
229
    (while (and (< arg 0) (not (bobp)))
230
      (if (and (not (looking-at parsep))
231
	       (re-search-backward "^\n" (max (1- (point)) (point-min)) t)
232
	       (looking-at parsep))
233
	  (setq arg (1+ arg))
234
	(setq start (point))
235
	;; Move back over paragraph-separating lines.
Joseph Arceneaux's avatar
Joseph Arceneaux committed
236
	(forward-char -1) (beginning-of-line)
237
	(while (and (not (bobp))
238
		    (progn (move-to-left-margin)
239
			   (looking-at parsep)))
Stefan Monnier's avatar
Typo.  
Stefan Monnier committed
240
	  (forward-line -1))
241 242
	(if (bobp)
	    nil
243
	  (setq arg (1+ arg))
244 245 246 247
	  ;; Go to end of the previous (non-separating) line.
	  (end-of-line)
	  ;; Search back for line that starts or separates paragraphs.
	  (if (if fill-prefix-regexp
248
		  ;; There is a fill prefix; it overrides parstart.
249
		  (let (multiple-lines)
250 251
		    (while (and (progn (beginning-of-line) (not (bobp)))
				(progn (move-to-left-margin)
252
				       (not (looking-at parsep)))
253
				(looking-at fill-prefix-regexp))
Stefan Monnier's avatar
Typo.  
Stefan Monnier committed
254 255
		      (unless (= (point) start)
			(setq multiple-lines t))
256
		      (forward-line -1))
257
		    (move-to-left-margin)
258 259 260 261 262 263 264 265
		    ;; This deleted code caused a long hanging-indent line
		    ;; not to be filled together with the following lines.
		    ;; ;; Don't move back over a line before the paragraph
		    ;; ;; which doesn't start with fill-prefix
		    ;; ;; unless that is the only line we've moved over.
		    ;; (and (not (looking-at fill-prefix-regexp))
		    ;;      multiple-lines
		    ;;      (forward-line 1))
266
		    (not (bobp)))
267
		(while (and (re-search-backward sp-parstart nil 1)
268
			    (setq found-start t)
269
			    ;; Found a candidate, but need to check if it is a
270
			    ;; REAL parstart.
271 272
			    (progn (setq start (point))
				   (move-to-left-margin)
273 274 275 276 277
				   (not (looking-at parsep)))
			    (not (and (looking-at parstart)
				      (or (not use-hard-newlines)
					  (get-text-property (1- start) 'hard)
					  (bobp)))))
278
		  (setq found-start nil)
279
		  (goto-char start))
280
		found-start)
281 282
	      ;; Found one.
	      (progn
283 284 285
		;; Move forward over paragraph separators.
		;; We know this cannot reach the place we started
		;; because we know we moved back over a non-separator.
286 287
		(while (and (not (eobp))
			    (progn (move-to-left-margin)
288
				   (looking-at parsep)))
289
		  (forward-line 1))
290 291 292 293 294 295 296
		;; If line before paragraph is just margin, back up to there.
		(end-of-line 0)
		(if (> (current-column) (current-left-margin))
		    (forward-char 1)
		  (skip-chars-backward " \t")
		  (if (not (bolp))
		      (forward-line 1))))
297
	    ;; No starter or separator line => use buffer beg.
298
	    (goto-char (point-min))))))
299

300
    (while (and (> arg 0) (not (eobp)))
301 302 303 304 305 306 307 308
      ;; Move forward over separator lines...
      (while (and (not (eobp))
		  (progn (move-to-left-margin) (not (eobp)))
		  (looking-at parsep))
	(forward-line 1))
      (unless (eobp) (setq arg (1- arg)))
      ;; ... and one more line.
      (forward-line 1)
Joseph Arceneaux's avatar
Joseph Arceneaux committed
309
      (if fill-prefix-regexp
310
	  ;; There is a fill prefix; it overrides parstart.
Joseph Arceneaux's avatar
Joseph Arceneaux committed
311
	  (while (and (not (eobp))
312
		      (progn (move-to-left-margin) (not (eobp)))
313
		      (not (looking-at parsep))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
314 315
		      (looking-at fill-prefix-regexp))
	    (forward-line 1))
316
	(while (and (re-search-forward sp-parstart nil 1)
317 318
		    (progn (setq start (match-beginning 0))
			   (goto-char start)
319 320
			   (not (eobp)))
		    (progn (move-to-left-margin)
321 322
			   (not (looking-at parsep)))
		    (or (not (looking-at parstart))
323 324
			(and use-hard-newlines
			     (not (get-text-property (1- start) 'hard)))))
325 326
	  (forward-char 1))
	(if (< (point) (point-max))
327
	    (goto-char start))))
328 329 330
    (constrain-to-field nil opoint t)
    ;; Return the number of steps that could not be done.
    arg))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
331 332 333

(defun backward-paragraph (&optional arg)
  "Move backward to start of paragraph.
334 335
With argument ARG, do it ARG times;
a negative argument ARG = -N means move forward N paragraphs.
Joseph Arceneaux's avatar
Joseph Arceneaux committed
336

Brian Preble's avatar
Brian Preble committed
337 338 339 340 341 342 343
A paragraph start is the beginning of a line which is a
`first-line-of-paragraph' or which is ordinary text and follows a
paragraph-separating line; except: if the first real line of a
paragraph is preceded by a blank line, the paragraph starts at that
blank line.

See `forward-paragraph' for more information."
Joseph Arceneaux's avatar
Joseph Arceneaux committed
344 345 346 347
  (interactive "p")
  (or arg (setq arg 1))
  (forward-paragraph (- arg)))

348
(defun mark-paragraph (&optional arg)
Joseph Arceneaux's avatar
Joseph Arceneaux committed
349
  "Put point at beginning of this paragraph, mark at end.
350 351 352 353 354 355
The paragraph marked is the one that contains point or follows point.

With argument ARG, puts mark at end of a following paragraph, so that
the number of paragraphs marked equals ARG.

If ARG is negative, point is put at end of this paragraph, mark is put
356 357 358 359
at beginning of this or a previous paragraph.

If this command is repeated, it marks the next ARG paragraphs after (or
before, if arg is negative) the ones already marked."
360
  (interactive "p")
361 362 363 364 365 366 367 368 369 370 371 372 373
  (unless arg (setq arg 1))
  (when (zerop arg)
    (error "Cannot mark zero paragraphs"))
  (cond ((and (eq last-command this-command) (mark t))
	 (set-mark
	  (save-excursion
	    (goto-char (mark))
	    (forward-paragraph arg)
	    (point))))
	(t
	 (forward-paragraph arg)
	 (push-mark nil t t)
	 (backward-paragraph arg))))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
374 375 376 377 378

(defun kill-paragraph (arg)
  "Kill forward to end of paragraph.
With arg N, kill forward to Nth end of paragraph;
negative arg -N means kill backward to Nth start of paragraph."
379
  (interactive "p")
380
  (kill-region (point) (progn (forward-paragraph arg) (point))))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
381 382 383 384 385

(defun backward-kill-paragraph (arg)
  "Kill back to start of paragraph.
With arg N, kill back to Nth start of paragraph;
negative arg -N means kill forward to Nth end of paragraph."
386
  (interactive "p")
387
  (kill-region (point) (progn (backward-paragraph arg) (point))))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
388 389 390 391 392 393 394 395 396 397 398

(defun transpose-paragraphs (arg)
  "Interchange this (or next) paragraph with previous one."
  (interactive "*p")
  (transpose-subr 'forward-paragraph arg))

(defun start-of-paragraph-text ()
  (let ((opoint (point)) npoint)
    (forward-paragraph -1)
    (setq npoint (point))
    (skip-chars-forward " \t\n")
399 400 401 402 403
    ;; If the range of blank lines found spans the original start point,
    ;; try again from the beginning of it.
    ;; Must be careful to avoid infinite loop
    ;; when following a single return at start of buffer.
    (if (and (>= (point) opoint) (< npoint opoint))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
	(progn
	  (goto-char npoint)
	  (if (> npoint (point-min))
	      (start-of-paragraph-text))))))

(defun end-of-paragraph-text ()
  (let ((opoint (point)))
    (forward-paragraph 1)
    (if (eq (preceding-char) ?\n) (forward-char -1))
    (if (<= (point) opoint)
	(progn
	  (forward-char 1)
	  (if (< (point) (point-max))
	      (end-of-paragraph-text))))))

(defun forward-sentence (&optional arg)
420
  "Move forward to next `sentence-end'.  With argument, repeat.
Brian Preble's avatar
Brian Preble committed
421
With negative argument, move backward repeatedly to `sentence-beginning'.
Joseph Arceneaux's avatar
Joseph Arceneaux committed
422

Brian Preble's avatar
Brian Preble committed
423 424
The variable `sentence-end' is a regular expression that matches ends of
sentences.  Also, every paragraph boundary terminates sentences as well."
Joseph Arceneaux's avatar
Joseph Arceneaux committed
425 426
  (interactive "p")
  (or arg (setq arg 1))
427 428
  (let ((opoint (point))
        (sentence-end (sentence-end)))
429
    (while (< arg 0)
430 431 432 433 434 435
      (let ((pos (point))
	    (par-beg (save-excursion (start-of-paragraph-text) (point))))
       (if (and (re-search-backward sentence-end par-beg t)
		(or (< (match-end 0) pos)
		    (re-search-backward sentence-end par-beg t)))
	   (goto-char (match-end 0))
436 437 438 439 440 441 442 443 444
	 (goto-char par-beg)))
      (setq arg (1+ arg)))
    (while (> arg 0)
      (let ((par-end (save-excursion (end-of-paragraph-text) (point))))
       (if (re-search-forward sentence-end par-end t)
	   (skip-chars-backward " \t\n")
	 (goto-char par-end)))
      (setq arg (1- arg)))
    (constrain-to-field nil opoint t)))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
445

446 447 448
(defun repunctuate-sentences ()
  "Put two spaces at the end of sentences from point to the end of buffer.
It works using `query-replace-regexp'."
449
  (interactive)
450 451 452 453
  (query-replace-regexp "\\([]\"')]?\\)\\([.?!]\\)\\([]\"')]?\\) +"
			"\\1\\2\\3  "))


Joseph Arceneaux's avatar
Joseph Arceneaux committed
454 455
(defun backward-sentence (&optional arg)
  "Move backward to start of sentence.  With arg, do it arg times.
Brian Preble's avatar
Brian Preble committed
456
See `forward-sentence' for more information."
Joseph Arceneaux's avatar
Joseph Arceneaux committed
457 458 459 460 461 462 463
  (interactive "p")
  (or arg (setq arg 1))
  (forward-sentence (- arg)))

(defun kill-sentence (&optional arg)
  "Kill from point to end of sentence.
With arg, repeat; negative arg -N means kill back to Nth start of sentence."
464
  (interactive "p")
465
  (kill-region (point) (progn (forward-sentence arg) (point))))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
466 467 468 469

(defun backward-kill-sentence (&optional arg)
  "Kill back from point to start of sentence.
With arg, repeat, or kill forward to Nth end of sentence if negative arg -N."
470
  (interactive "p")
471
  (kill-region (point) (progn (backward-sentence arg) (point))))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
472 473

(defun mark-end-of-sentence (arg)
474 475 476
  "Put mark at end of sentence.  Arg works as in `forward-sentence'.
If this command is repeated, it marks the next ARG sentences after the
ones already marked."
Joseph Arceneaux's avatar
Joseph Arceneaux committed
477 478
  (interactive "p")
  (push-mark
479 480 481 482 483 484
   (save-excursion
     (if (and (eq last-command this-command) (mark t))
	 (goto-char (mark)))
     (forward-sentence arg)
     (point))
   nil t))
Joseph Arceneaux's avatar
Joseph Arceneaux committed
485 486 487 488 489

(defun transpose-sentences (arg)
  "Interchange this (next) and previous sentence."
  (interactive "*p")
  (transpose-subr 'forward-sentence arg))
Eric S. Raymond's avatar
Eric S. Raymond committed
490

491 492 493 494
;;; Local Variables:
;;; coding: iso-2022-7bit
;;; End:

Miles Bader's avatar
Miles Bader committed
495
;;; arch-tag: e727eb1a-527a-4464-b9d7-9d3ec0d1a575
Eric S. Raymond's avatar
Eric S. Raymond committed
496
;;; paragraphs.el ends here