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