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