1;;; paragraphs.el --- paragraph and sentence parsing 2 3;; Copyright (C) 1985, 1986, 1987, 1991, 1994, 1995, 1996, 1997, 1999, 2000, 4;; 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. 5 6;; Maintainer: FSF 7;; Keywords: wp 8 9;; This file is part of GNU Emacs. 10 11;; GNU Emacs is free software; you can redistribute it and/or modify 12;; it under the terms of the GNU General Public License as published by 13;; the Free Software Foundation; either version 2, or (at your option) 14;; any later version. 15 16;; GNU Emacs is distributed in the hope that it will be useful, 17;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19;; GNU General Public License for more details. 20 21;; You should have received a copy of the GNU General Public License 22;; along with GNU Emacs; see the file COPYING. If not, write to the 23;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 24;; Boston, MA 02110-1301, USA. 25 26;;; Commentary: 27 28;; This package provides the paragraph-oriented commands documented in the 29;; Emacs manual. 30 31;;; Code: 32 33(defgroup paragraphs nil 34 "Paragraph and sentence parsing." 35 :group 'editing) 36 37(put 'use-hard-newlines 'permanent-local t) 38(define-minor-mode use-hard-newlines 39 "Minor mode to distinguish hard and soft newlines. 40When active, the functions `newline' and `open-line' add the 41text-property `hard' to newlines that they insert, and a line is 42only considered as a candidate to match `paragraph-start' or 43`paragraph-separate' if it follows a hard newline. 44 45Prefix argument says to turn mode on if positive, off if negative. 46When the mode is turned on, if there are newlines in the buffer but no hard 47newlines, ask the user whether to mark as hard any newlines preceeding a 48`paragraph-start' line. From a program, second arg INSERT specifies whether 49to do this; it can be `never' to change nothing, t or `always' to force 50marking, `guess' to try to do the right thing with no questions, nil 51or anything else to ask the user. 52 53Newlines not marked hard are called \"soft\", and are always internal 54to paragraphs. The fill functions insert and delete only soft newlines." 55 :group 'paragraphs 56 :extra-args (insert) 57 (when use-hard-newlines 58 ;; Turn mode on 59 ;; Intuit hard newlines -- 60 ;; mark as hard any newlines preceding a paragraph-start line. 61 (if (or (eq insert t) (eq insert 'always) 62 (and (not (eq 'never insert)) 63 (not (text-property-any (point-min) (point-max) 'hard t)) 64 (save-excursion 65 (goto-char (point-min)) 66 (search-forward "\n" nil t)) 67 (or (eq insert 'guess) 68 (y-or-n-p "Make newlines between paragraphs hard? ")))) 69 (save-excursion 70 (goto-char (point-min)) 71 (while (search-forward "\n" nil t) 72 (let ((pos (point))) 73 (move-to-left-margin) 74 (when (looking-at paragraph-start) 75 (set-hard-newline-properties (1- pos) pos)) 76 ;; If paragraph-separate, newline after it is hard too. 77 (when (looking-at paragraph-separate) 78 (set-hard-newline-properties (1- pos) pos) 79 (end-of-line) 80 (unless (eobp) 81 (set-hard-newline-properties (point) (1+ (point))))))))))) 82 83(defcustom paragraph-start "\f\\|[ \t]*$" "\ 84Regexp for beginning of a line that starts OR separates paragraphs. 85This regexp should match lines that separate paragraphs 86and should also match lines that start a paragraph 87\(and are part of that paragraph). 88 89This is matched against the text at the left margin, which is not necessarily 90the beginning of the line, so it should never use \"^\" as an anchor. This 91ensures that the paragraph functions will work equally well within a region 92of text indented by a margin setting. 93 94The variable `paragraph-separate' specifies how to distinguish 95lines that start paragraphs from lines that separate them. 96 97If the variable `use-hard-newlines' is non-nil, then only lines following a 98hard newline are considered to match." 99 :group 'paragraphs 100 :type 'regexp) 101;;;###autoload(put 'paragraph-start 'safe-local-variable 'stringp) 102 103;; paragraph-start requires a hard newline, but paragraph-separate does not: 104;; It is assumed that paragraph-separate is distinctive enough to be believed 105;; whenever it occurs, while it is reasonable to set paragraph-start to 106;; something very minimal, even including "." (which makes every hard newline 107;; start a new paragraph). 108 109(defcustom paragraph-separate "[ \t\f]*$" 110 "Regexp for beginning of a line that separates paragraphs. 111If you change this, you may have to change `paragraph-start' also. 112 113This is matched against the text at the left margin, which is not necessarily 114the beginning of the line, so it should not use \"^\" as an anchor. This 115ensures that the paragraph functions will work equally within a region of 116text indented by a margin setting." 117 :group 'paragraphs 118 :type 'regexp) 119;;;###autoload(put 'paragraph-separate 'safe-local-variable 'stringp) 120 121(defcustom sentence-end-double-space t 122 "Non-nil means a single space does not end a sentence. 123This is relevant for filling. See also `sentence-end-without-period' 124and `colon-double-space'. 125 126This value is used by the function `sentence-end' to construct the 127regexp describing the end of a sentence, when the value of the variable 128`sentence-end' is nil. See Info node `(elisp)Standard Regexps'." 129 :type 'boolean 130 :group 'fill) 131;;;###autoload(put 'sentence-end-double-space 'safe-local-variable 'booleanp) 132 133(defcustom sentence-end-without-period nil 134 "Non-nil means a sentence will end without a period. 135For example, a sentence in Thai text ends with double space but 136without a period. 137 138This value is used by the function `sentence-end' to construct the 139regexp describing the end of a sentence, when the value of the variable 140`sentence-end' is nil. See Info node `(elisp)Standard Regexps'." 141 :type 'boolean 142 :group 'fill) 143;;;###autoload(put 'sentence-end-without-period 'safe-local-variable 'booleanp) 144 145(defcustom sentence-end-without-space 146 "$B!#!%!)!*$A!##.#?#!$(0!$!%!)!*$(G!$!%!)!*(B" 147 "String of characters that end sentence without following spaces. 148 149This value is used by the function `sentence-end' to construct the 150regexp describing the end of a sentence, when the value of the variable 151`sentence-end' is nil. See Info node `(elisp)Standard Regexps'." 152 :group 'paragraphs 153 :type 'string) 154;;;###autoload(put 'sentence-end-without-space 'safe-local-variable 'stringp) 155 156(defcustom sentence-end nil 157 "Regexp describing the end of a sentence. 158The value includes the whitespace following the sentence. 159All paragraph boundaries also end sentences, regardless. 160 161The value nil means to use the default value defined by the 162function `sentence-end'. You should always use this function 163to obtain the value of this variable." 164 :group 'paragraphs 165 :type '(choice regexp (const :tag "Use default value" nil))) 166;;;###autoload(put 'sentence-end 'safe-local-variable 'string-or-null-p) 167 168(defcustom sentence-end-base "[.?!][]\"'$B!I$,1r}(B)}]*" 169 "Regexp matching the basic end of a sentence, not including following space." 170 :group 'paragraphs 171 :type 'string 172 :version "22.1") 173;;;###autoload(put 'sentence-end-base 'safe-local-variable 'stringp) 174 175(defun sentence-end () 176 "Return the regexp describing the end of a sentence. 177 178This function returns either the value of the variable `sentence-end' 179if it is non-nil, or the default value constructed from the 180variables `sentence-end-base', `sentence-end-double-space', 181`sentence-end-without-period' and `sentence-end-without-space'. 182 183The default value specifies that in order to be recognized as the 184end of a sentence, the ending period, question mark, or exclamation point 185must be followed by two spaces, with perhaps some closing delimiters 186in between. See Info node `(elisp)Standard Regexps'." 187 (or sentence-end 188 (concat (if sentence-end-without-period "\\w \\|") 189 "\\(" 190 sentence-end-base 191 (if sentence-end-double-space 192 "\\($\\| $\\|\t\\| \\)" "\\($\\|[\t ]\\)") 193 "\\|[" sentence-end-without-space "]+" 194 "\\)" 195 "[ \t\n]*"))) 196 197(defcustom page-delimiter "^\014" 198 "Regexp describing line-beginnings that separate pages." 199 :group 'paragraphs 200 :type 'regexp) 201;;;###autoload(put 'page-delimiter 'safe-local-variable 'stringp) 202 203(defcustom paragraph-ignore-fill-prefix nil 204 "Non-nil means the paragraph commands are not affected by `fill-prefix'. 205This is desirable in modes where blank lines are the paragraph delimiters." 206 :group 'paragraphs 207 :type 'boolean) 208;;;###autoload(put 'paragraph-ignore-fill-prefix 'safe-local-variable 'booleanp) 209 210(defun forward-paragraph (&optional arg) 211 "Move forward to end of paragraph. 212With argument ARG, do it ARG times; 213a negative argument ARG = -N means move backward N paragraphs. 214 215A line which `paragraph-start' matches either separates paragraphs 216\(if `paragraph-separate' matches it also) or is the first line of a paragraph. 217A paragraph end is the beginning of a line which is not part of the paragraph 218to which the end of the previous line belongs, or the end of the buffer. 219Returns the count of paragraphs left to move." 220 (interactive "p") 221 (or arg (setq arg 1)) 222 (let* ((opoint (point)) 223 (fill-prefix-regexp 224 (and fill-prefix (not (equal fill-prefix "")) 225 (not paragraph-ignore-fill-prefix) 226 (regexp-quote fill-prefix))) 227 ;; Remove ^ from paragraph-start and paragraph-sep if they are there. 228 ;; These regexps shouldn't be anchored, because we look for them 229 ;; starting at the left-margin. This allows paragraph commands to 230 ;; work normally with indented text. 231 ;; This hack will not find problem cases like "whatever\\|^something". 232 (parstart (if (and (not (equal "" paragraph-start)) 233 (equal ?^ (aref paragraph-start 0))) 234 (substring paragraph-start 1) 235 paragraph-start)) 236 (parsep (if (and (not (equal "" paragraph-separate)) 237 (equal ?^ (aref paragraph-separate 0))) 238 (substring paragraph-separate 1) 239 paragraph-separate)) 240 (parsep 241 (if fill-prefix-regexp 242 (concat parsep "\\|" 243 fill-prefix-regexp "[ \t]*$") 244 parsep)) 245 ;; This is used for searching. 246 (sp-parstart (concat "^[ \t]*\\(?:" parstart "\\|" parsep "\\)")) 247 start found-start) 248 (while (and (< arg 0) (not (bobp))) 249 (if (and (not (looking-at parsep)) 250 (re-search-backward "^\n" (max (1- (point)) (point-min)) t) 251 (looking-at parsep)) 252 (setq arg (1+ arg)) 253 (setq start (point)) 254 ;; Move back over paragraph-separating lines. 255 (forward-char -1) (beginning-of-line) 256 (while (and (not (bobp)) 257 (progn (move-to-left-margin) 258 (looking-at parsep))) 259 (forward-line -1)) 260 (if (bobp) 261 nil 262 (setq arg (1+ arg)) 263 ;; Go to end of the previous (non-separating) line. 264 (end-of-line) 265 ;; Search back for line that starts or separates paragraphs. 266 (if (if fill-prefix-regexp 267 ;; There is a fill prefix; it overrides parstart. 268 (let (multiple-lines) 269 (while (and (progn (beginning-of-line) (not (bobp))) 270 (progn (move-to-left-margin) 271 (not (looking-at parsep))) 272 (looking-at fill-prefix-regexp)) 273 (unless (= (point) start) 274 (setq multiple-lines t)) 275 (forward-line -1)) 276 (move-to-left-margin) 277 ;; This deleted code caused a long hanging-indent line 278 ;; not to be filled together with the following lines. 279 ;; ;; Don't move back over a line before the paragraph 280 ;; ;; which doesn't start with fill-prefix 281 ;; ;; unless that is the only line we've moved over. 282 ;; (and (not (looking-at fill-prefix-regexp)) 283 ;; multiple-lines 284 ;; (forward-line 1)) 285 (not (bobp))) 286 (while (and (re-search-backward sp-parstart nil 1) 287 (setq found-start t) 288 ;; Found a candidate, but need to check if it is a 289 ;; REAL parstart. 290 (progn (setq start (point)) 291 (move-to-left-margin) 292 (not (looking-at parsep))) 293 (not (and (looking-at parstart) 294 (or (not use-hard-newlines) 295 (bobp) 296 (get-text-property 297 (1- start) 'hard))))) 298 (setq found-start nil) 299 (goto-char start)) 300 found-start) 301 ;; Found one. 302 (progn 303 ;; Move forward over paragraph separators. 304 ;; We know this cannot reach the place we started 305 ;; because we know we moved back over a non-separator. 306 (while (and (not (eobp)) 307 (progn (move-to-left-margin) 308 (looking-at parsep))) 309 (forward-line 1)) 310 ;; If line before paragraph is just margin, back up to there. 311 (end-of-line 0) 312 (if (> (current-column) (current-left-margin)) 313 (forward-char 1) 314 (skip-chars-backward " \t") 315 (if (not (bolp)) 316 (forward-line 1)))) 317 ;; No starter or separator line => use buffer beg. 318 (goto-char (point-min)))))) 319 320 (while (and (> arg 0) (not (eobp))) 321 ;; Move forward over separator lines... 322 (while (and (not (eobp)) 323 (progn (move-to-left-margin) (not (eobp))) 324 (looking-at parsep)) 325 (forward-line 1)) 326 (unless (eobp) (setq arg (1- arg))) 327 ;; ... and one more line. 328 (forward-line 1) 329 (if fill-prefix-regexp 330 ;; There is a fill prefix; it overrides parstart. 331 (while (and (not (eobp)) 332 (progn (move-to-left-margin) (not (eobp))) 333 (not (looking-at parsep)) 334 (looking-at fill-prefix-regexp)) 335 (forward-line 1)) 336 (while (and (re-search-forward sp-parstart nil 1) 337 (progn (setq start (match-beginning 0)) 338 (goto-char start) 339 (not (eobp))) 340 (progn (move-to-left-margin) 341 (not (looking-at parsep))) 342 (or (not (looking-at parstart)) 343 (and use-hard-newlines 344 (not (get-text-property (1- start) 'hard))))) 345 (forward-char 1)) 346 (if (< (point) (point-max)) 347 (goto-char start)))) 348 (constrain-to-field nil opoint t) 349 ;; Return the number of steps that could not be done. 350 arg)) 351 352(defun backward-paragraph (&optional arg) 353 "Move backward to start of paragraph. 354With argument ARG, do it ARG times; 355a negative argument ARG = -N means move forward N paragraphs. 356 357A paragraph start is the beginning of a line which is a 358`first-line-of-paragraph' or which is ordinary text and follows a 359paragraph-separating line; except: if the first real line of a 360paragraph is preceded by a blank line, the paragraph starts at that 361blank line. 362 363See `forward-paragraph' for more information." 364 (interactive "p") 365 (or arg (setq arg 1)) 366 (forward-paragraph (- arg))) 367 368(defun mark-paragraph (&optional arg allow-extend) 369 "Put point at beginning of this paragraph, mark at end. 370The paragraph marked is the one that contains point or follows point. 371 372With argument ARG, puts mark at end of a following paragraph, so that 373the number of paragraphs marked equals ARG. 374 375If ARG is negative, point is put at end of this paragraph, mark is put 376at beginning of this or a previous paragraph. 377 378Interactively, if this command is repeated 379or (in Transient Mark mode) if the mark is active, 380it marks the next ARG paragraphs after the ones already marked." 381 (interactive "p\np") 382 (unless arg (setq arg 1)) 383 (when (zerop arg) 384 (error "Cannot mark zero paragraphs")) 385 (cond ((and allow-extend 386 (or (and (eq last-command this-command) (mark t)) 387 (and transient-mark-mode mark-active))) 388 (set-mark 389 (save-excursion 390 (goto-char (mark)) 391 (forward-paragraph arg) 392 (point)))) 393 (t 394 (forward-paragraph arg) 395 (push-mark nil t t) 396 (backward-paragraph arg)))) 397 398(defun kill-paragraph (arg) 399 "Kill forward to end of paragraph. 400With arg N, kill forward to Nth end of paragraph; 401negative arg -N means kill backward to Nth start of paragraph." 402 (interactive "p") 403 (kill-region (point) (progn (forward-paragraph arg) (point)))) 404 405(defun backward-kill-paragraph (arg) 406 "Kill back to start of paragraph. 407With arg N, kill back to Nth start of paragraph; 408negative arg -N means kill forward to Nth end of paragraph." 409 (interactive "p") 410 (kill-region (point) (progn (backward-paragraph arg) (point)))) 411 412(defun transpose-paragraphs (arg) 413 "Interchange this (or next) paragraph with previous one." 414 (interactive "*p") 415 (transpose-subr 'forward-paragraph arg)) 416 417(defun start-of-paragraph-text () 418 (let ((opoint (point)) npoint) 419 (forward-paragraph -1) 420 (setq npoint (point)) 421 (skip-chars-forward " \t\n") 422 ;; If the range of blank lines found spans the original start point, 423 ;; try again from the beginning of it. 424 ;; Must be careful to avoid infinite loop 425 ;; when following a single return at start of buffer. 426 (if (and (>= (point) opoint) (< npoint opoint)) 427 (progn 428 (goto-char npoint) 429 (if (> npoint (point-min)) 430 (start-of-paragraph-text)))))) 431 432(defun end-of-paragraph-text () 433 (let ((opoint (point))) 434 (forward-paragraph 1) 435 (if (eq (preceding-char) ?\n) (forward-char -1)) 436 (if (<= (point) opoint) 437 (progn 438 (forward-char 1) 439 (if (< (point) (point-max)) 440 (end-of-paragraph-text)))))) 441 442(defun forward-sentence (&optional arg) 443 "Move forward to next `sentence-end'. With argument, repeat. 444With negative argument, move backward repeatedly to `sentence-beginning'. 445 446The variable `sentence-end' is a regular expression that matches ends of 447sentences. Also, every paragraph boundary terminates sentences as well." 448 (interactive "p") 449 (or arg (setq arg 1)) 450 (let ((opoint (point)) 451 (sentence-end (sentence-end))) 452 (while (< arg 0) 453 (let ((pos (point)) 454 (par-beg (save-excursion (start-of-paragraph-text) (point)))) 455 (if (and (re-search-backward sentence-end par-beg t) 456 (or (< (match-end 0) pos) 457 (re-search-backward sentence-end par-beg t))) 458 (goto-char (match-end 0)) 459 (goto-char par-beg))) 460 (setq arg (1+ arg))) 461 (while (> arg 0) 462 (let ((par-end (save-excursion (end-of-paragraph-text) (point)))) 463 (if (re-search-forward sentence-end par-end t) 464 (skip-chars-backward " \t\n") 465 (goto-char par-end))) 466 (setq arg (1- arg))) 467 (constrain-to-field nil opoint t))) 468 469(defun repunctuate-sentences () 470 "Put two spaces at the end of sentences from point to the end of buffer. 471It works using `query-replace-regexp'." 472 (interactive) 473 (query-replace-regexp "\\([]\"')]?\\)\\([.?!]\\)\\([]\"')]?\\) +" 474 "\\1\\2\\3 ")) 475 476 477(defun backward-sentence (&optional arg) 478 "Move backward to start of sentence. With arg, do it arg times. 479See `forward-sentence' for more information." 480 (interactive "p") 481 (or arg (setq arg 1)) 482 (forward-sentence (- arg))) 483 484(defun kill-sentence (&optional arg) 485 "Kill from point to end of sentence. 486With arg, repeat; negative arg -N means kill back to Nth start of sentence." 487 (interactive "p") 488 (kill-region (point) (progn (forward-sentence arg) (point)))) 489 490(defun backward-kill-sentence (&optional arg) 491 "Kill back from point to start of sentence. 492With arg, repeat, or kill forward to Nth end of sentence if negative arg -N." 493 (interactive "p") 494 (kill-region (point) (progn (backward-sentence arg) (point)))) 495 496(defun mark-end-of-sentence (arg) 497 "Put mark at end of sentence. Arg works as in `forward-sentence'. 498If this command is repeated, it marks the next ARG sentences after the 499ones already marked." 500 (interactive "p") 501 (push-mark 502 (save-excursion 503 (if (and (eq last-command this-command) (mark t)) 504 (goto-char (mark))) 505 (forward-sentence arg) 506 (point)) 507 nil t)) 508 509(defun transpose-sentences (arg) 510 "Interchange this (next) and previous sentence." 511 (interactive "*p") 512 (transpose-subr 'forward-sentence arg)) 513 514;; Local Variables: 515;; coding: iso-2022-7bit 516;; End: 517 518;; arch-tag: e727eb1a-527a-4464-b9d7-9d3ec0d1a575 519;;; paragraphs.el ends here 520