Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to make `fill-paragraph` stop at the end of a sentence?

Tags:

emacs

It is sometimes desirable to start each sentence in a paragraph on a separate line. For instance, this makes it easier to diff large text documents, because a change in one sentence will not affect the entire paragraph. Some markup systems (e.g. *roff) also require each sentence to start on a new line.

Is there a way, e.g. by judicious redefinition of paragraph-separate and paragraph-start, to make fill-paragraph stop between sentences?

(note: I use Emacs 23.3.1)


Update: sample mdoc (*roff) markup:

The
.Nm
utility makes a series of passes with increasing block sizes.
In each pass, it either reads or writes (or both) a number of
non-consecutive blocks at increasing offsets relative to the ideal
alignment, which is assumed to be multiples of the block size.
The results are presented in terms of time elapsed, transactions per
second and kB per second.

This is a single paragraph with three sentences, each of which starts on a separate line even though there is room for the first word(s) on the previous line. Currently, fill-paragraph will transform this into

The
.Nm
utility makes a series of passes with increasing block sizes.  In each
pass, it either reads or writes (or both) a number of non-consecutive
blocks at increasing offsets relative to the ideal alignment, which is
assumed to be multiples of the block size.  The results are presented
in terms of time elapsed, transactions per second and kB per second.

which is what I want to avoid.


Update: in re sentences and paragraphs

I see that my question is a bit unclear, because I used the term "paragraph" to refer both to what Emacs calls a paragraph and to what ends up as a continuous block of text in the output of whatever processor I use (groff, latex etc.). To clarify,

  • I need to keep the sentences together without any blank lines between them; groff doesn't like blank lines, while latex sees them as paragraph separators.
  • I need fill-paragraph to operate on individual sentences, i.e. I want to redefine a paragraph as something that starts after either a blank line or the end of the previous paragraph, and ends with a period followed by either a newline character or at least two whitespace characters.
  • I would love to have fill-paragraph break a block of text apart into individual sentences, but I don't think it can be done easily.

For instance, if I type the following:

The
.Nm
utility makes a series of passes with increasing block sizes.
In each pass, it either reads or writes (or both) a number of non-consecutive blocks at increasing offsets relative to the ideal alignment, which is assumed to be multiples of the block size.
The results are presented in terms of time elapsed, transactions per second and kB per second.

then move the point to the line that starts with "In each pass" and press M-q, I should get the following:

The
.Nm
utility makes a series of passes with increasing block sizes.
In each pass, it either reads or writes (or both) a number of
non-consecutive blocks at increasing offsets relative to the ideal
alignment, which is assumed to be multiples of the block size.
The results are presented in terms of time elapsed, transactions per second and kB per second.

Note that the last sentence is untouched.

like image 754
DES Avatar asked Sep 26 '11 11:09

DES


2 Answers

How about telling paragraph-start to look for any line that starts with a capital letter:

"\f\\|[     ]*$\\|^[A-Z]"

Note that the new part is \\^[A-Z]

That should work for most cases, you'd only have to watch for the rare cases where you have a capital mid-sentence, and that sentence happens to be long enough to break just before that mid-sentence word.

EDIT: you probably want to account for indentation too:

"\f\\|[     ]*$\\|^[    ]*[A-Z]"

The space between the square brackets contains a space and a tab.

EDIT: you need to turn off case-fold-search for this to work, otherwise capitals and lower case letters are not distinguished in the match!

EDIT: if you want to turn off case-fold-search for just this function, bind the following to M-q (which you can do locally or globally, as you see fit).

(defun my-fill-paragraph ()
  (interactive)
  (let ((case-fold-search nil))
    (fill-paragraph)))
like image 128
Tyler Avatar answered Jan 01 '23 15:01

Tyler


Does this DTRT?

(defun separate-sentences (&optional beg end)
  "ensure each sentence ends with a new line.
When no region specified, use current paragraph."
  (interactive (when (use-region-p)
                   (list (region-beginning) (region-end))))
  (unless (and beg end)
    (save-excursion
      (forward-paragraph -1)
      (setq beg (point))
      (forward-paragraph 1)
      (setq end (point))))
  (setq end (if (markerp end)
                end
              (set-marker (make-marker) end)))
  (save-excursion
    (goto-char beg)
    (while (re-search-forward (sentence-end) end t)
      (unless (or (looking-at-p "[ \t]*$")
                  (looking-back "^[ \t]*"))
        (insert "\n")))))

(defun fill-paragraph-sentence-groups (justify)
  "Groups of sentences filled together.  A sentence ending with newline marks end of group."
  (save-excursion
    (save-restriction
      (narrow-to-region (progn (forward-paragraph -1) (point))
                        (progn (forward-paragraph 1) (point)))
      (goto-char (point-min))
      (skip-chars-forward " \t\n")
      (while (not (or (looking-at-p paragraph-separate)
                      (eobp)))
        (fill-region (point)
                     (progn
                       (loop do (forward-sentence 1)
                             until (looking-at "[ \t]*$"))
                       (point))
                     justify)
        (unless (looking-back "^[ \t]*")
          (forward-line 1)))
      t)))

(defun fill-paragraph-sentence-individual (justify)
  "Each sentence in paragraph is put on new line."
  (save-excursion
    (separate-sentences)
    (fill-paragraph-sentence-groups justify)))

;; deployment option 1: add to major-mode hook

(add-hook 'text-mode-hook (lambda ()
                            (set (make-local-variable fill-paragraph-function) 'fill-paragraph-sentence-individual)))

;; deployment option 2: call my-fill-paragraph any where

(defun my-fill-paragraph (arg)
  (interactive "*P")
  (let ((fill-paragraph-function 'fill-paragraph-sentence-individual))
    (fill-paragraph arg)))

Two paragraph filling functions are presented above. One grouping sentences that don't end on new line together. Another breaking every sentence into a new line.

I only show how to deploy the individual one because that's what the OP wants. Follow the model to deploy the groups version if you wish.

like image 29
event_jr Avatar answered Jan 01 '23 17:01

event_jr