tags:

views:

729

answers:

5

I've seen at least two recommendations on StackOverflow to insert newlines between sentences when editing LaTeX documents. The reason being that the practice facilitates source control, diffing, and collaborative editing.

I'm basically convinced, but I'm lazy, and I don't want to have to think about it.

So I'm searching for some emacs incantation to handle it for me. Could be a minor mode, could be a set of variables that need to be set.

I think what I don't want is

  • Soft wrapping of text (say using the longlines and (set long-lines-auto-wrap 't)). This is because I don't want to impose requirements on my collaborators' editors, and I sometimes use other unix tools to examine these files.

I think what I do want is

  • For fill-paragraph to fill between newlines that look like they mark the end of a sentence.
  • A solution that works with auto-fill-mode would be a bonus.

That is:

chat chat chat.
A new sentence
with goofed up wrapping that needs to be fixed.
Mumble mumble

Transformed to:

chat chat chat.
A new sentence with goofed up wrapping that needs to be fixed.
Mumble mumble

Your comments and suggestions are appreciated.


Edit: The suggestion by Jouni K. Seppänen pointed me at LaTeX-fill-break-at-separators, which suggests that emacs almost knows how to do this already. Anyway, I'm off to read some code, and will report back. Thanks again.


More general version of the same question: http://stackoverflow.com/questions/543379/editor-showdown-maintain-newlines-at-the-ends-of-sentences. Thanks, dreeves.

+1  A: 

If you put a comment marker at the end of each sentence, Emacs knows not to move the next line inside the comment:

chat chat chat.%
A new sentence
with goofed up wrapping that needs to be fixed.%
Mumble mumble%

Then M-q fills each sentence separately, at least in AUCTeX 11.85. (If you test this in Emacs, there seems to be a bug where if this is the first paragraph in the buffer and you type M-q, you get an error message. Just put a newline before the text to work around it.)

If you don't want to type the comment characters, you could take LaTeX-fill-paragraph and modify it so that sentence-ending punctuation at end of line works similarly to comments.

Jouni K. Seppänen
OK. Not what I had been expecting, but it would certainly work.
dmckee
Isn't this is more trouble than just switching autofill off? What use is M-q in this situation?
Charles Stewart
+1  A: 

I am assuming you know elisp.

There are a few approaches you can take:

  • Hook into auto-fill-mode. There are a lot of hard-coded conditionals there, so it might not work for you. You can potentially play with auto-fill-function and see if you have the hook you need there.

  • Make a character (probably .) "electric" so that when you press it, it inserts itself and then calls a function to determine how to fill the line you're on.

  • Set an after-change-hook to call a function that determines how to fill the sentence. This function will be called after every change to the buffer, so do it efficiently. (This mechanism is used by font-lock, so don't worry about it too much. It sounds slow, but really isn't -- people type slowly.)

Once you have hooked in at the right place, you just have to implement the filling logic. The source for sentence-at-point (from thingatpt) may be instructive.

Anyway, I've never heard of anyone doing this... but it is definitely possible. Like most things in Emacs, it's just a Simple Matter Of Programming.

jrockway
I've never given lisp the time it requires, so I am very weak there.
dmckee
'k. This may be the perfect learning project, since it is straightforward, but not "easy".
jrockway
+2  A: 

May not work in all circumstances, but:

(defun my-fill-sentence ()
  "Fill sentence separated by punctuation or blank lines."
  (interactive)
  (let (start end)
    (save-excursion
      (re-search-backward "\\(^\\s-*$\\|[.?!]\\)" nil t)
      (skip-syntax-forward "^w")
      (setq start (point-at-bol)))
    (save-excursion
      (re-search-forward "\\(^\\s-*$\\|[.?!]\\)" nil t)
      (setq end (point-at-eol)))
    (save-restriction
      (narrow-to-region start end)
      (fill-paragraph nil))))

To make it work with auto-fill-mode, add (setq normal-auto-fill-function 'my-fill-sentence) to your LaTeX mode hook (I think).

scottfrazer
It looks like the fill-paragraph there at the end is sticking in a newline I don't want. But is does work only on the sentence under the point.
dmckee
It works on your example ... is there a different example that shows the problem?
scottfrazer
Um. Didn't work for me on the same example. If I put the put after "sentence" an invoke it I get the second sentence nicely filled and a newline before "Mumble mumble". Looking in my '.emacs' I find a lot of cruft. I'll turn it off and try again.
dmckee
+1  A: 

If the other answers are too automatic, here's a semiautomatic approach. It's basically what you would do repeatedly if you were going to manually reformat, but condensed so you can hit a single key repeatedly instead.

;; - go to the end of the line,
;; - do ^d to suck the previous line onto this one, 
;; - make sure there's only one space between the now-concatenated
;;   lines, and then 
;; - jump to the end and hit space so that (with auto-fill-mode)
;;   the line nicely rewraps itself:
;;   (turn on auto-fill-mode with M-x auto-fill-mode)
(defalias 'fill-sentence
  (read-kbd-macro "C-e C-d SPC M-x just- one- space RET C-e SPC <backspace>"))

(define-key global-map [f4] 'fill-sentence)  ; or whatever key you like
dreeves
+4  A: 

Here's what I use, which was mostly cribbed from Luca de Alfaro:

(defun fill-sentence ()
  (interactive)
  (save-excursion
    (or (eq (point) (point-max)) (forward-char))
    (forward-sentence -1)
    (indent-relative t)
    (let ((beg (point))
          (ix (string-match "LaTeX" mode-name)))
      (forward-sentence)
      (if (and ix (equal "LaTeX" (substring mode-name ix)))
          (LaTeX-fill-region-as-paragraph beg (point))
        (fill-region-as-paragraph beg (point))))))

I bind this to M-j with

(global-set-key (kbd "M-j") 'fill-sentence)

The references to "LaTeX" are for AUCTeX support. If you don't use AUCTeX, the let can be simplified to

(let (beg (point))
  (forward-sentence)
  (fill-region-as-paragraph beg (point)))
Chris Conway
I use AUCTeX. And I'll give this a try.
dmckee
Be nice to have a way to make this happen after every self-insert-command......
vy32