textmodes/paragraphs.el. Undo previous accidental patch.
[bpt/emacs.git] / lisp / longlines.el
CommitLineData
aac42093 1;;; longlines.el --- automatically wrap long lines -*- coding:utf-8 -*-
cf6ffd8c 2
73b0cd50 3;; Copyright (C) 2000-2001, 2004-2011 Free Software Foundation, Inc.
cf6ffd8c
RS
4
5;; Authors: Kai Grossjohann <Kai.Grossjohann@CS.Uni-Dortmund.DE>
6;; Alex Schroeder <alex@gnu.org>
7;; Chong Yidong <cyd@stupidchicken.com>
8;; Maintainer: Chong Yidong <cyd@stupidchicken.com>
a145b41c 9;; Keywords: convenience, wp
cf6ffd8c
RS
10
11;; This file is part of GNU Emacs.
12
eb3fa2cf 13;; GNU Emacs is free software: you can redistribute it and/or modify
cf6ffd8c 14;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
cf6ffd8c
RS
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
eb3fa2cf 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
cf6ffd8c
RS
25
26;;; Commentary:
27
28;; Some text editors save text files with long lines, and they
29;; automatically break these lines at whitespace, without actually
30;; inserting any newline characters. When doing `M-q' in Emacs, you
31;; are inserting newline characters. Longlines mode provides a file
32;; format which wraps the long lines when reading a file and unwraps
33;; the lines when saving the file. It can also wrap and unwrap
34;; automatically as editing takes place.
35
36;; Special thanks to Rod Smith for many useful bug reports.
37
38;;; Code:
39
cf6ffd8c
RS
40(defgroup longlines nil
41 "Automatic wrapping of long lines when loading files."
42 :group 'fill)
43
44(defcustom longlines-auto-wrap t
da95a9c8 45 "Non-nil means long lines are automatically wrapped after each command.
cf6ffd8c
RS
46Otherwise, you can perform filling using `fill-paragraph' or
47`auto-fill-mode'. In any case, the soft newlines will be removed
48when the file is saved to disk."
49 :group 'longlines
50 :type 'boolean)
51
52(defcustom longlines-wrap-follows-window-size nil
da95a9c8 53 "Non-nil means wrapping and filling happen at the edge of the window.
cf6ffd8c
RS
54Otherwise, `fill-column' is used, regardless of the window size. This
55does not work well when the buffer is displayed in multiple windows
c455889d
CY
56with differing widths.
57
58If the value is an integer, that specifies the distance from the
59right edge of the window at which wrapping occurs. For any other
60non-nil value, wrapping occurs 2 characters from the right edge."
cf6ffd8c
RS
61 :group 'longlines
62 :type 'boolean)
63
64(defcustom longlines-show-hard-newlines nil
da95a9c8 65 "Non-nil means each hard newline is marked on the screen.
c29316d5 66\(The variable `longlines-show-effect' controls what they look like.)
cf6ffd8c 67You can also enable the display temporarily, using the command
f0770b09 68`longlines-show-hard-newlines'."
cf6ffd8c
RS
69 :group 'longlines
70 :type 'boolean)
71
12e18d7d 72(defcustom longlines-show-effect (propertize "¶\n" 'face 'escape-glyph)
da95a9c8 73 "A string to display when showing hard newlines.
cf6ffd8c
RS
74This is used when `longlines-show-hard-newlines' is on."
75 :group 'longlines
76 :type 'string)
77
78;; Internal variables
79
80(defvar longlines-wrap-beg nil)
81(defvar longlines-wrap-end nil)
82(defvar longlines-wrap-point nil)
83(defvar longlines-showing nil)
8a7e2b23 84(defvar longlines-decoded nil)
cf6ffd8c
RS
85
86(make-variable-buffer-local 'longlines-wrap-beg)
87(make-variable-buffer-local 'longlines-wrap-end)
88(make-variable-buffer-local 'longlines-wrap-point)
89(make-variable-buffer-local 'longlines-showing)
8a7e2b23 90(make-variable-buffer-local 'longlines-decoded)
cf6ffd8c
RS
91
92;; Mode
93
fc5e09b3
DN
94(defvar message-indent-citation-function)
95
cf6ffd8c
RS
96;;;###autoload
97(define-minor-mode longlines-mode
36adf6ce 98 "Minor mode to wrap long lines.
cf6ffd8c
RS
99In Long Lines mode, long lines are wrapped if they extend beyond
100`fill-column'. The soft newlines used for line wrapping will not
101show up when the text is yanked or saved to disk.
102
75bfc667
JL
103With no argument, this command toggles Long Lines mode.
104With a prefix argument ARG, turn Long Lines minor mode on if ARG is positive,
36adf6ce
LMI
105otherwise turn it off.
106
c29316d5 107If the variable `longlines-auto-wrap' is non-nil, lines are automatically
cf6ffd8c
RS
108wrapped whenever the buffer is changed. You can always call
109`fill-paragraph' to fill individual paragraphs.
110
c29316d5
RS
111If the variable `longlines-show-hard-newlines' is non-nil, hard newlines
112are indicated with a symbol."
6da6c2fe 113 :group 'longlines :lighter " ll"
cf6ffd8c
RS
114 (if longlines-mode
115 ;; Turn on longlines mode
116 (progn
117 (use-hard-newlines 1 'never)
118 (set (make-local-variable 'require-final-newline) nil)
119 (add-to-list 'buffer-file-format 'longlines)
120 (add-hook 'change-major-mode-hook 'longlines-mode-off nil t)
b39aa4fd 121 (add-hook 'before-revert-hook 'longlines-before-revert-hook nil t)
cf6ffd8c 122 (make-local-variable 'buffer-substring-filters)
a1d155a4 123 (make-local-variable 'longlines-auto-wrap)
930aae96 124 (set (make-local-variable 'isearch-search-fun-function)
7f5bb182 125 'longlines-search-function)
e87085e6
CY
126 (set (make-local-variable 'replace-search-function)
127 'longlines-search-forward)
128 (set (make-local-variable 'replace-re-search-function)
129 'longlines-re-search-forward)
cf6ffd8c
RS
130 (add-to-list 'buffer-substring-filters 'longlines-encode-string)
131 (when longlines-wrap-follows-window-size
c455889d
CY
132 (let ((dw (if (and (integerp longlines-wrap-follows-window-size)
133 (>= longlines-wrap-follows-window-size 0)
134 (< longlines-wrap-follows-window-size
135 (window-width)))
136 longlines-wrap-follows-window-size
137 2)))
138 (set (make-local-variable 'fill-column)
139 (- (window-width) dw)))
cf6ffd8c
RS
140 (add-hook 'window-configuration-change-hook
141 'longlines-window-change-function nil t))
142 (let ((buffer-undo-list t)
6fd388f3 143 (inhibit-read-only t)
a145b41c 144 (after-change-functions nil)
91b53ad5
MR
145 (mod (buffer-modified-p))
146 buffer-file-name buffer-file-truename)
cf6ffd8c
RS
147 ;; Turning off undo is OK since (spaces + newlines) is
148 ;; conserved, except for a corner case in
149 ;; longlines-wrap-lines that we'll never encounter from here
e7b382ed
CY
150 (save-restriction
151 (widen)
8a7e2b23
CY
152 (unless longlines-decoded
153 (longlines-decode-buffer)
154 (setq longlines-decoded t))
eb0d2864 155 (longlines-wrap-region (point-min) (point-max)))
cf6ffd8c
RS
156 (set-buffer-modified-p mod))
157 (when (and longlines-show-hard-newlines
158 (not longlines-showing))
159 (longlines-show-hard-newlines))
0f157ad5
CY
160
161 ;; Hacks to make longlines play nice with various modes.
162 (cond ((eq major-mode 'mail-mode)
15575807 163 (add-hook 'mail-setup-hook 'longlines-decode-buffer nil t)
0f157ad5
CY
164 (or mail-citation-hook
165 (add-hook 'mail-citation-hook 'mail-indent-citation nil t))
166 (add-hook 'mail-citation-hook 'longlines-decode-region nil t))
167 ((eq major-mode 'message-mode)
2c127d45 168 (add-hook 'message-setup-hook 'longlines-decode-buffer nil t)
0f157ad5
CY
169 (make-local-variable 'message-indent-citation-function)
170 (if (not (listp message-indent-citation-function))
171 (setq message-indent-citation-function
172 (list message-indent-citation-function)))
173 (add-to-list 'message-indent-citation-function
174 'longlines-decode-region t)))
426f8573 175
a1d155a4
CY
176 (add-hook 'after-change-functions 'longlines-after-change-function nil t)
177 (add-hook 'post-command-hook 'longlines-post-command-function nil t)
426f8573 178 (when longlines-auto-wrap
a1d155a4 179 (auto-fill-mode 0)))
cf6ffd8c
RS
180 ;; Turn off longlines mode
181 (setq buffer-file-format (delete 'longlines buffer-file-format))
182 (if longlines-showing
183 (longlines-unshow-hard-newlines))
6fd388f3 184 (let ((buffer-undo-list t)
a145b41c 185 (after-change-functions nil)
91b53ad5
MR
186 (inhibit-read-only t)
187 buffer-file-name buffer-file-truename)
8a7e2b23
CY
188 (if longlines-decoded
189 (save-restriction
190 (widen)
191 (longlines-encode-region (point-min) (point-max))
192 (setq longlines-decoded nil))))
cf6ffd8c 193 (remove-hook 'change-major-mode-hook 'longlines-mode-off t)
cf6ffd8c
RS
194 (remove-hook 'after-change-functions 'longlines-after-change-function t)
195 (remove-hook 'post-command-hook 'longlines-post-command-function t)
b39aa4fd 196 (remove-hook 'before-revert-hook 'longlines-before-revert-hook t)
cf6ffd8c
RS
197 (remove-hook 'window-configuration-change-hook
198 'longlines-window-change-function t)
6fd388f3
CY
199 (when longlines-wrap-follows-window-size
200 (kill-local-variable 'fill-column))
930aae96 201 (kill-local-variable 'isearch-search-fun-function)
e87085e6
CY
202 (kill-local-variable 'replace-search-function)
203 (kill-local-variable 'replace-re-search-function)
6fd388f3
CY
204 (kill-local-variable 'require-final-newline)
205 (kill-local-variable 'buffer-substring-filters)
206 (kill-local-variable 'use-hard-newlines)))
cf6ffd8c
RS
207
208(defun longlines-mode-off ()
209 "Turn off longlines mode.
210This function exists to be called by `change-major-mode-hook' when the
211major mode changes."
212 (longlines-mode 0))
213
214;; Showing the effect of hard newlines in the buffer
215
cf6ffd8c
RS
216(defun longlines-show-hard-newlines (&optional arg)
217 "Make hard newlines visible by adding a face.
218With optional argument ARG, make the hard newlines invisible again."
219 (interactive "P")
cf6ffd8c
RS
220 (if arg
221 (longlines-unshow-hard-newlines)
222 (setq longlines-showing t)
fc0eafe1 223 (longlines-show-region (point-min) (point-max))))
cf6ffd8c
RS
224
225(defun longlines-show-region (beg end)
226 "Make hard newlines between BEG and END visible."
227 (let* ((pmin (min beg end))
228 (pmax (max beg end))
da95a9c8 229 (pos (text-property-not-all pmin pmax 'hard nil))
fc0eafe1
MR
230 (mod (buffer-modified-p))
231 (buffer-undo-list t)
232 (inhibit-read-only t)
91b53ad5
MR
233 (inhibit-modification-hooks t)
234 buffer-file-name buffer-file-truename)
cf6ffd8c
RS
235 (while pos
236 (put-text-property pos (1+ pos) 'display
fc0eafe1
MR
237 (copy-sequence longlines-show-effect))
238 (setq pos (text-property-not-all (1+ pos) pmax 'hard nil)))
239 (restore-buffer-modified-p mod)))
cf6ffd8c
RS
240
241(defun longlines-unshow-hard-newlines ()
242 "Make hard newlines invisible again."
243 (interactive)
244 (setq longlines-showing nil)
fc0eafe1
MR
245 (let ((pos (text-property-not-all (point-min) (point-max) 'hard nil))
246 (mod (buffer-modified-p))
247 (buffer-undo-list t)
248 (inhibit-read-only t)
91b53ad5
MR
249 (inhibit-modification-hooks t)
250 buffer-file-name buffer-file-truename)
cf6ffd8c
RS
251 (while pos
252 (remove-text-properties pos (1+ pos) '(display))
fc0eafe1
MR
253 (setq pos (text-property-not-all (1+ pos) (point-max) 'hard nil)))
254 (restore-buffer-modified-p mod)))
cf6ffd8c
RS
255
256;; Wrapping the paragraphs.
257
258(defun longlines-wrap-region (beg end)
259 "Wrap each successive line, starting with the line before BEG.
260Stop when we reach lines after END that don't need wrapping, or the
261end of the buffer."
85a0b368
CY
262 (let ((mod (buffer-modified-p)))
263 (setq longlines-wrap-point (point))
264 (goto-char beg)
265 (forward-line -1)
266 ;; Two successful longlines-wrap-line's in a row mean successive
267 ;; lines don't need wrapping.
268 (while (null (and (longlines-wrap-line)
269 (or (eobp)
270 (and (>= (point) end)
271 (longlines-wrap-line))))))
272 (goto-char longlines-wrap-point)
273 (set-buffer-modified-p mod)))
cf6ffd8c
RS
274
275(defun longlines-wrap-line ()
276 "If the current line needs to be wrapped, wrap it and return nil.
277If wrapping is performed, point remains on the line. If the line does
278not need to be wrapped, move point to the next line and return t."
279 (if (longlines-set-breakpoint)
cee723fb
CY
280 (progn (insert-before-markers ?\n)
281 (backward-char 1)
282 (delete-char -1)
283 (forward-char 1)
cf6ffd8c
RS
284 nil)
285 (if (longlines-merge-lines-p)
286 (progn (end-of-line)
cf6ffd8c
RS
287 ;; After certain commands (e.g. kill-line), there may be two
288 ;; successive soft newlines in the buffer. In this case, we
289 ;; replace these two newlines by a single space. Unfortunately,
290 ;; this breaks the conservation of (spaces + newlines), so we
291 ;; have to fiddle with longlines-wrap-point.
e5ad37ee
DK
292 (if (or (prog1 (bolp) (forward-char 1)) (eolp))
293 (progn
294 (delete-char -1)
295 (if (> longlines-wrap-point (point))
296 (setq longlines-wrap-point
297 (1- longlines-wrap-point))))
443012f0 298 (insert-before-markers-and-inherit ?\s)
e5ad37ee
DK
299 (backward-char 1)
300 (delete-char -1)
301 (forward-char 1))
cf6ffd8c
RS
302 nil)
303 (forward-line 1)
304 t)))
305
306(defun longlines-set-breakpoint ()
307 "Place point where we should break the current line, and return t.
308If the line should not be broken, return nil; point remains on the
309line."
310 (move-to-column fill-column)
311 (if (and (re-search-forward "[^ ]" (line-end-position) 1)
312 (> (current-column) fill-column))
313 ;; This line is too long. Can we break it?
314 (or (longlines-find-break-backward)
315 (progn (move-to-column fill-column)
316 (longlines-find-break-forward)))))
317
318(defun longlines-find-break-backward ()
319 "Move point backward to the first available breakpoint and return t.
320If no breakpoint is found, return nil."
321 (and (search-backward " " (line-beginning-position) 1)
322 (save-excursion
323 (skip-chars-backward " " (line-beginning-position))
324 (null (bolp)))
325 (progn (forward-char 1)
326 (if (and fill-nobreak-predicate
327 (run-hook-with-args-until-success
328 'fill-nobreak-predicate))
329 (progn (skip-chars-backward " " (line-beginning-position))
330 (longlines-find-break-backward))
331 t))))
332
333(defun longlines-find-break-forward ()
334 "Move point forward to the first available breakpoint and return t.
335If no break point is found, return nil."
336 (and (search-forward " " (line-end-position) 1)
337 (progn (skip-chars-forward " " (line-end-position))
338 (null (eolp)))
339 (if (and fill-nobreak-predicate
340 (run-hook-with-args-until-success
341 'fill-nobreak-predicate))
342 (longlines-find-break-forward)
343 t)))
344
345(defun longlines-merge-lines-p ()
346 "Return t if part of the next line can fit onto the current line.
347Otherwise, return nil. Text cannot be moved across hard newlines."
348 (save-excursion
349 (end-of-line)
350 (and (null (eobp))
351 (null (get-text-property (point) 'hard))
352 (let ((space (- fill-column (current-column))))
353 (forward-line 1)
354 (if (eq (char-after) ? )
355 t ; We can always merge some spaces
356 (<= (if (search-forward " " (line-end-position) 1)
357 (current-column)
358 (1+ (current-column)))
359 space))))))
360
0f157ad5
CY
361(defun longlines-decode-region (&optional beg end)
362 "Turn all newlines between BEG and END into hard newlines.
363If BEG and END are nil, the point and mark are used."
364 (if (null beg) (setq beg (point)))
365 (if (null end) (setq end (mark t)))
cf6ffd8c 366 (save-excursion
eb0d2864
CY
367 (let ((reg-max (max beg end)))
368 (goto-char (min beg end))
369 (while (search-forward "\n" reg-max t)
370 (set-hard-newline-properties
371 (match-beginning 0) (match-end 0))))))
cf6ffd8c 372
2c127d45
CY
373(defun longlines-decode-buffer ()
374 "Turn all newlines in the buffer into hard newlines."
375 (longlines-decode-region (point-min) (point-max)))
376
06b60517 377(defun longlines-encode-region (beg end &optional _buffer)
cf6ffd8c
RS
378 "Replace each soft newline between BEG and END with exactly one space.
379Hard newlines are left intact. The optional argument BUFFER exists for
380compatibility with `format-alist', and is ignored."
381 (save-excursion
eb0d2864
CY
382 (let ((reg-max (max beg end))
383 (mod (buffer-modified-p)))
cf6ffd8c 384 (goto-char (min beg end))
eb0d2864 385 (while (search-forward "\n" reg-max t)
cf6ffd8c
RS
386 (unless (get-text-property (match-beginning 0) 'hard)
387 (replace-match " ")))
388 (set-buffer-modified-p mod)
389 end)))
390
391(defun longlines-encode-string (string)
392 "Return a copy of STRING with each soft newline replaced by a space.
393Hard newlines are left intact."
394 (let* ((str (copy-sequence string))
395 (pos (string-match "\n" str)))
396 (while pos
397 (if (null (get-text-property pos 'hard str))
398 (aset str pos ? ))
399 (setq pos (string-match "\n" str (1+ pos))))
400 str))
401
402;; Auto wrap
403
404(defun longlines-auto-wrap (&optional arg)
a1d155a4
CY
405 "Toggle automatic line wrapping.
406With optional argument ARG, turn on line wrapping if and only if ARG is positive.
407If automatic line wrapping is turned on, wrap the entire buffer."
cf6ffd8c 408 (interactive "P")
a1d155a4
CY
409 (setq arg (if arg
410 (> (prefix-numeric-value arg) 0)
411 (not longlines-auto-wrap)))
cf6ffd8c 412 (if arg
85a0b368 413 (progn
a1d155a4
CY
414 (setq longlines-auto-wrap t)
415 (longlines-wrap-region (point-min) (point-max))
a1d155a4
CY
416 (message "Auto wrap enabled."))
417 (setq longlines-auto-wrap nil)
418 (message "Auto wrap disabled.")))
cf6ffd8c 419
06b60517 420(defun longlines-after-change-function (beg end _len)
cf6ffd8c
RS
421 "Update `longlines-wrap-beg' and `longlines-wrap-end'.
422This is called by `after-change-functions' to keep track of the region
423that has changed."
a1d155a4 424 (when (and longlines-auto-wrap (not undo-in-progress))
cf6ffd8c
RS
425 (setq longlines-wrap-beg
426 (if longlines-wrap-beg (min longlines-wrap-beg beg) beg))
427 (setq longlines-wrap-end
428 (if longlines-wrap-end (max longlines-wrap-end end) end))))
429
430(defun longlines-post-command-function ()
431 "Perform line wrapping on the parts of the buffer that have changed.
432This is called by `post-command-hook' after each command."
a1d155a4 433 (when (and longlines-auto-wrap longlines-wrap-beg)
e17833bc
CY
434 (if (or (eq this-command 'yank)
435 (eq this-command 'yank-pop))
436 (longlines-decode-region (point) (mark t)))
437 (if longlines-showing
438 (longlines-show-region longlines-wrap-beg longlines-wrap-end))
cf6ffd8c
RS
439 (unless (or (eq this-command 'fill-paragraph)
440 (eq this-command 'fill-region))
441 (longlines-wrap-region longlines-wrap-beg longlines-wrap-end))
442 (setq longlines-wrap-beg nil)
443 (setq longlines-wrap-end nil)))
444
445(defun longlines-window-change-function ()
446 "Re-wrap the buffer if the window width has changed.
a3545af4 447This is called by `window-configuration-change-hook'."
c455889d
CY
448 (let ((dw (if (and (integerp longlines-wrap-follows-window-size)
449 (>= longlines-wrap-follows-window-size 0)
450 (< longlines-wrap-follows-window-size (window-width)))
451 longlines-wrap-follows-window-size
452 2)))
453 (when (/= fill-column (- (window-width) dw))
454 (setq fill-column (- (window-width) dw))
455 (longlines-wrap-region (point-min) (point-max)))))
cf6ffd8c 456
930aae96
CY
457;; Isearch
458
7f5bb182 459(defun longlines-search-function ()
930aae96
CY
460 (cond
461 (isearch-word
462 (if isearch-forward 'word-search-forward 'word-search-backward))
463 (isearch-regexp
464 (if isearch-forward 're-search-forward 're-search-backward))
465 (t
466 (if isearch-forward
467 'longlines-search-forward
468 'longlines-search-backward))))
469
470(defun longlines-search-forward (string &optional bound noerror count)
667adde1 471 (let ((search-spaces-regexp " *[ \n]"))
930aae96
CY
472 (re-search-forward (regexp-quote string) bound noerror count)))
473
474(defun longlines-search-backward (string &optional bound noerror count)
667adde1 475 (let ((search-spaces-regexp " *[ \n]"))
930aae96
CY
476 (re-search-backward (regexp-quote string) bound noerror count)))
477
e87085e6 478(defun longlines-re-search-forward (string &optional bound noerror count)
667adde1 479 (let ((search-spaces-regexp " *[ \n]"))
e87085e6
CY
480 (re-search-forward string bound noerror count)))
481
cf6ffd8c
RS
482;; Loading and saving
483
b39aa4fd
CY
484(defun longlines-before-revert-hook ()
485 (add-hook 'after-revert-hook 'longlines-after-revert-hook nil t)
486 (longlines-mode 0))
487
488(defun longlines-after-revert-hook ()
489 (remove-hook 'after-revert-hook 'longlines-after-revert-hook t)
490 (longlines-mode 1))
491
cf6ffd8c
RS
492(add-to-list
493 'format-alist
c613b39c 494 (list 'longlines "Automatically wrap long lines." nil nil
b39aa4fd 495 'longlines-encode-region t nil))
cf6ffd8c 496
c172e101
JB
497;; Unloading
498
499(defun longlines-unload-function ()
500 "Unload the longlines library."
501 (save-current-buffer
502 (dolist (buffer (buffer-list))
503 (set-buffer buffer)
504 (longlines-mode-off)))
505 ;; continue standard unloading
506 nil)
507
cf6ffd8c
RS
508(provide 'longlines)
509
510;;; longlines.el ends here