longlines.el 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. ;;; longlines.el --- automatically wrap long lines
  2. ;; Copyright (C) 2000, 2001, 2004, 2005 Free Software Foundation, Inc.
  3. ;; Authors: Kai Grossjohann <Kai.Grossjohann@CS.Uni-Dortmund.DE>
  4. ;; Alex Schroeder <alex@gnu.org>
  5. ;; Chong Yidong <cyd@stupidchicken.com>
  6. ;; Maintainer: Chong Yidong <cyd@stupidchicken.com>
  7. ;; Version: 2.3.4
  8. ;; Keywords: convenience, wp
  9. ;; This file is free software; you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation; either version 2, or (at your option)
  12. ;; any later version.
  13. ;; This file is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  19. ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  20. ;; Boston, MA 02110-1301, USA.
  21. ;;; Commentary:
  22. ;; Some text editors save text files with long lines, and they
  23. ;; automatically break these lines at whitespace, without actually
  24. ;; inserting any newline characters. When doing `M-q' in Emacs, you
  25. ;; are inserting newline characters. Longlines mode provides a file
  26. ;; format which wraps the long lines when reading a file and unwraps
  27. ;; the lines when saving the file. It can also wrap and unwrap
  28. ;; automatically as editing takes place.
  29. ;; Special thanks to Rod Smith for many useful bug reports.
  30. ;;; Installation:
  31. ;; Save longlines.el in a convenient directory, preferably in
  32. ;; your `load-path'. Add the following to your `user-init-file':
  33. ;;
  34. ;; (autoload 'longlines-mode
  35. ;; "longlines.el"
  36. ;; "Minor mode for automatically wrapping long lines." t)
  37. ;;; Todo:
  38. ;; Investigate ^M characters showing up in relation to auto-save-file,
  39. ;; at least for NTEmacs.
  40. ;;; Code:
  41. (require 'easy-mmode)
  42. (if (featurep 'xemacs)
  43. (eval-and-compile
  44. (require 'overlay)
  45. (defvar fill-nobreak-predicate nil)
  46. (defvar undo-in-progress nil)
  47. (defvar longlines-mode-hook nil)
  48. (defvar longlines-mode-on-hook nil)
  49. (defvar longlines-mode-off-hook nil)
  50. (unless (functionp 'line-end-position)
  51. (defun line-end-position ()
  52. (save-excursion (end-of-line) (point))))
  53. (unless (functionp 'line-beginning-position)
  54. (defun line-beginning-position (&optional n)
  55. (save-excursion
  56. (if n (forward-line n))
  57. (beginning-of-line)
  58. (point))))))
  59. (defgroup longlines nil
  60. "Automatic wrapping of long lines when loading files."
  61. :group 'fill)
  62. (defcustom longlines-auto-wrap t
  63. "*Non-nil means long lines are automatically wrapped after each command.
  64. Otherwise, you can perform filling using `fill-paragraph' or
  65. `auto-fill-mode'. In any case, the soft newlines will be removed
  66. when the file is saved to disk."
  67. :group 'longlines
  68. :type 'boolean)
  69. (defcustom longlines-wrap-follows-window-size nil
  70. "*Non-nil means wrapping and filling happen at the edge of the window.
  71. Otherwise, `fill-column' is used, regardless of the window size. This
  72. does not work well when the buffer is displayed in multiple windows
  73. with differing widths."
  74. :group 'longlines
  75. :type 'boolean)
  76. (defcustom longlines-show-hard-newlines nil
  77. "*Non-nil means each hard newline is marked on the screen.
  78. \(The variable `longlines-show-effect' controls what they look like.)
  79. You can also enable the display temporarily, using the command
  80. `longlines-show-hard-newlines'"
  81. :group 'longlines
  82. :type 'boolean)
  83. (defface longlines-visible-face
  84. '((t (:foreground "cyan")))
  85. "Face used to make hard newlines visible in `longlines-mode'.")
  86. (defcustom longlines-show-effect (propertize "|" 'face 'longlines-visible-face)
  87. "*A string to display when showing hard newlines.
  88. This is used when `longlines-show-hard-newlines' is on."
  89. :group 'longlines
  90. :type 'string)
  91. ;; Internal variables
  92. (defvar longlines-wrap-beg nil)
  93. (defvar longlines-wrap-end nil)
  94. (defvar longlines-wrap-point nil)
  95. (defvar longlines-showing nil)
  96. (make-variable-buffer-local 'longlines-wrap-beg)
  97. (make-variable-buffer-local 'longlines-wrap-end)
  98. (make-variable-buffer-local 'longlines-wrap-point)
  99. (make-variable-buffer-local 'longlines-showing)
  100. ;; Mode
  101. ;;;###autoload
  102. (define-minor-mode longlines-mode
  103. "Toggle Long Lines mode.
  104. In Long Lines mode, long lines are wrapped if they extend beyond
  105. `fill-column'. The soft newlines used for line wrapping will not
  106. show up when the text is yanked or saved to disk.
  107. If the variable `longlines-auto-wrap' is non-nil, lines are automatically
  108. wrapped whenever the buffer is changed. You can always call
  109. `fill-paragraph' to fill individual paragraphs.
  110. If the variable `longlines-show-hard-newlines' is non-nil, hard newlines
  111. are indicated with a symbol."
  112. :group 'longlines :lighter " ll"
  113. (if longlines-mode
  114. ;; Turn on longlines mode
  115. (progn
  116. (use-hard-newlines 1 'never)
  117. (add-to-list 'buffer-file-format 'longlines)
  118. (add-hook 'change-major-mode-hook 'longlines-mode-off nil t)
  119. (add-hook 'before-revert-hook 'longlines-before-revert-hook nil t)
  120. (when longlines-wrap-follows-window-size
  121. (set (make-local-variable 'fill-column)
  122. (- (window-width) window-min-width))
  123. (add-hook 'window-configuration-change-hook
  124. 'longlines-window-change-function nil t))
  125. (let ((buffer-undo-list t)
  126. (inhibit-read-only t)
  127. (after-change-functions nil)
  128. (mod (buffer-modified-p)))
  129. ;; Turning off undo is OK since (spaces + newlines) is
  130. ;; conserved, except for a corner case in
  131. ;; longlines-wrap-lines that we'll never encounter from here
  132. (save-restriction
  133. (widen)
  134. (longlines-decode-buffer)
  135. (longlines-wrap-region (point-min) (point-max)))
  136. (set-buffer-modified-p mod))
  137. (when (and longlines-show-hard-newlines
  138. (not longlines-showing))
  139. (longlines-show-hard-newlines))
  140. (when longlines-auto-wrap
  141. (auto-fill-mode 0)
  142. (add-hook 'after-change-functions
  143. 'longlines-after-change-function nil t)
  144. (add-hook 'post-command-hook
  145. 'longlines-post-command-function nil t)))
  146. ;; Turn off longlines mode
  147. (setq buffer-file-format (delete 'longlines buffer-file-format))
  148. (if longlines-showing
  149. (longlines-unshow-hard-newlines))
  150. (let ((buffer-undo-list t)
  151. (after-change-functions nil)
  152. (inhibit-read-only t))
  153. (save-restriction
  154. (widen)
  155. (longlines-encode-region (point-min) (point-max))))
  156. (remove-hook 'change-major-mode-hook 'longlines-mode-off t)
  157. (remove-hook 'after-change-functions 'longlines-after-change-function t)
  158. (remove-hook 'post-command-hook 'longlines-post-command-function t)
  159. (remove-hook 'before-revert-hook 'longlines-before-revert-hook t)
  160. (remove-hook 'window-configuration-change-hook
  161. 'longlines-window-change-function t)
  162. (when longlines-wrap-follows-window-size
  163. (kill-local-variable 'fill-column))
  164. (kill-local-variable 'use-hard-newlines)))
  165. (defun longlines-mode-off ()
  166. "Turn off longlines mode.
  167. This function exists to be called by `change-major-mode-hook' when the
  168. major mode changes."
  169. (longlines-mode 0))
  170. ;; Showing the effect of hard newlines in the buffer
  171. (defun longlines-show-hard-newlines (&optional arg)
  172. "Make hard newlines visible by adding a face.
  173. With optional argument ARG, make the hard newlines invisible again
  174. by calling `longlines-unshow-hard-newlines'."
  175. (interactive "P")
  176. (if arg
  177. (longlines-unshow-hard-newlines)
  178. (save-excursion
  179. (setq longlines-showing t)
  180. ;; We want to show every single newline. Therefore every single
  181. ;; newline must get its own overlay. No spanning of multiple
  182. ;; newlines with one overlay.
  183. (let ((pos (text-property-any (point-min) (point-max) 'hard t)))
  184. (while pos
  185. (longlines-show-region pos (1+ pos))
  186. (setq pos (text-property-any (1+ pos) (point-max) 'hard t)))))))
  187. (defun longlines-show-region (start end)
  188. "Make region between START and END visible."
  189. (unless (let ((os (overlays-in start end))
  190. o exists)
  191. (while (and os (not exists))
  192. (setq o (car os)
  193. os (cdr os))
  194. (when (overlay-get o 'longlines)
  195. (setq exists t)))
  196. exists)
  197. (let ((o (make-overlay start end nil t)))
  198. (if (consp longlines-show-effect)
  199. (overlay-put o (car longlines-show-effect) (cdr longlines-show-effect))
  200. (overlay-put o 'before-string longlines-show-effect))
  201. (overlay-put o 'longlines t)
  202. (overlay-put o 'evaporate t))))
  203. (defun longlines-unshow-hard-newlines ()
  204. "Make hard newlines invisible again."
  205. (interactive)
  206. (setq longlines-showing nil)
  207. (let ((os (overlays-in (point-min) (point-max)))
  208. o)
  209. (while os
  210. (setq o (car os)
  211. os (cdr os))
  212. (when (overlay-get o 'longlines)
  213. (delete-overlay o)))))
  214. ;; Wrapping the paragraphs.
  215. (defun longlines-wrap-region (beg end)
  216. "Wrap each successive line, starting with the line before BEG.
  217. Stop when we reach lines after END that don't need wrapping, or the
  218. end of the buffer."
  219. (setq longlines-wrap-point (point))
  220. (goto-char beg)
  221. (forward-line -1)
  222. ;; Two successful longlines-wrap-line's in a row mean successive
  223. ;; lines don't need wrapping.
  224. (while (null (and (longlines-wrap-line)
  225. (or (eobp)
  226. (and (>= (point) end)
  227. (longlines-wrap-line))))))
  228. (goto-char longlines-wrap-point))
  229. (defun longlines-wrap-line ()
  230. "If the current line needs to be wrapped, wrap it and return nil.
  231. If wrapping is performed, point remains on the line. If the line does
  232. not need to be wrapped, move point to the next line and return t."
  233. (if (longlines-set-breakpoint)
  234. (progn (insert-before-markers ?\n)
  235. (backward-char 1)
  236. (delete-char -1)
  237. (forward-char 1)
  238. nil)
  239. (if (longlines-merge-lines-p)
  240. (progn (end-of-line)
  241. ;; After certain commands (e.g. kill-line), there may be two
  242. ;; successive soft newlines in the buffer. In this case, we
  243. ;; replace these two newlines by a single space. Unfortunately,
  244. ;; this breaks the conservation of (spaces + newlines), so we
  245. ;; have to fiddle with longlines-wrap-point.
  246. (if (or (prog1 (bolp) (forward-char 1)) (eolp))
  247. (progn
  248. (delete-char -1)
  249. (if (> longlines-wrap-point (point))
  250. (setq longlines-wrap-point
  251. (1- longlines-wrap-point))))
  252. (insert-before-markers-and-inherit ?\ )
  253. (backward-char 1)
  254. (delete-char -1)
  255. (forward-char 1))
  256. nil)
  257. (forward-line 1)
  258. t)))
  259. (defun longlines-set-breakpoint ()
  260. "Place point where we should break the current line, and return t.
  261. If the line should not be broken, return nil; point remains on the
  262. line."
  263. (move-to-column fill-column)
  264. (if (and (re-search-forward "[^ ]" (line-end-position) 1)
  265. (> (current-column) fill-column))
  266. ;; This line is too long. Can we break it?
  267. (or (longlines-find-break-backward)
  268. (progn (move-to-column fill-column)
  269. (longlines-find-break-forward)))))
  270. (defun longlines-find-break-backward ()
  271. "Move point backward to the first available breakpoint and return t.
  272. If no breakpoint is found, return nil."
  273. (and (search-backward " " (line-beginning-position) 1)
  274. (save-excursion
  275. (skip-chars-backward " " (line-beginning-position))
  276. (null (bolp)))
  277. (progn (forward-char 1)
  278. (if (and fill-nobreak-predicate
  279. (run-hook-with-args-until-success
  280. 'fill-nobreak-predicate))
  281. (progn (skip-chars-backward " " (line-beginning-position))
  282. (longlines-find-break-backward))
  283. t))))
  284. (defun longlines-find-break-forward ()
  285. "Move point forward to the first available breakpoint and return t.
  286. If no break point is found, return nil."
  287. (and (search-forward " " (line-end-position) 1)
  288. (progn (skip-chars-forward " " (line-end-position))
  289. (null (eolp)))
  290. (if (and fill-nobreak-predicate
  291. (run-hook-with-args-until-success
  292. 'fill-nobreak-predicate))
  293. (longlines-find-break-forward)
  294. t)))
  295. (defun longlines-merge-lines-p ()
  296. "Return t if part of the next line can fit onto the current line.
  297. Otherwise, return nil. Text cannot be moved across hard newlines."
  298. (save-excursion
  299. (end-of-line)
  300. (and (null (eobp))
  301. (null (get-text-property (point) 'hard))
  302. (let ((space (- fill-column (current-column))))
  303. (forward-line 1)
  304. (if (eq (char-after) ? )
  305. t ; We can always merge some spaces
  306. (<= (if (search-forward " " (line-end-position) 1)
  307. (current-column)
  308. (1+ (current-column)))
  309. space))))))
  310. (defun longlines-decode-region (&optional beg end)
  311. "Turn all newlines between BEG and END into hard newlines.
  312. If BEG and END are nil, the point and mark are used."
  313. (if (null beg) (setq beg (point)))
  314. (if (null end) (setq end (mark t)))
  315. (save-excursion
  316. (let ((reg-max (max beg end)))
  317. (goto-char (min beg end))
  318. (while (search-forward "\n" reg-max t)
  319. (set-hard-newline-properties
  320. (match-beginning 0) (match-end 0))))))
  321. (defun longlines-decode-buffer ()
  322. "Turn all newlines in the buffer into hard newlines."
  323. (longlines-decode-region (point-min) (point-max)))
  324. (defun longlines-encode-region (beg end &optional buffer)
  325. "Replace each soft newline between BEG and END with exactly one space.
  326. Hard newlines are left intact. The optional argument BUFFER exists for
  327. compatibility with `format-alist', and is ignored."
  328. (save-excursion
  329. (let ((reg-max (max beg end))
  330. (mod (buffer-modified-p)))
  331. (goto-char (min beg end))
  332. (while (search-forward "\n" reg-max t)
  333. (unless (get-text-property (match-beginning 0) 'hard)
  334. (replace-match " ")))
  335. (set-buffer-modified-p mod)
  336. end)))
  337. ;; Auto wrap
  338. (defun longlines-auto-wrap (&optional arg)
  339. "Turn on automatic line wrapping, and wrap the entire buffer.
  340. With optional argument ARG, turn off line wrapping."
  341. (interactive "P")
  342. (remove-hook 'after-change-functions 'longlines-after-change-function t)
  343. (remove-hook 'post-command-hook 'longlines-post-command-function t)
  344. (if arg
  345. (progn (setq longlines-auto-wrap nil)
  346. (message "Auto wrap disabled."))
  347. (setq longlines-auto-wrap t)
  348. (add-hook 'after-change-functions
  349. 'longlines-after-change-function nil t)
  350. (add-hook 'post-command-hook
  351. 'longlines-post-command-function nil t)
  352. (let ((mod (buffer-modified-p)))
  353. (longlines-wrap-region (point-min) (point-max))
  354. (set-buffer-modified-p mod))
  355. (message "Auto wrap enabled.")))
  356. (defun longlines-after-change-function (beg end len)
  357. "Update `longlines-wrap-beg' and `longlines-wrap-end'.
  358. This is called by `after-change-functions' to keep track of the region
  359. that has changed."
  360. (unless undo-in-progress
  361. (setq longlines-wrap-beg
  362. (if longlines-wrap-beg (min longlines-wrap-beg beg) beg))
  363. (setq longlines-wrap-end
  364. (if longlines-wrap-end (max longlines-wrap-end end) end))))
  365. (defun longlines-post-command-function ()
  366. "Perform line wrapping on the parts of the buffer that have changed.
  367. This is called by `post-command-hook' after each command."
  368. (when longlines-wrap-beg
  369. (cond ((or (eq this-command 'yank)
  370. (eq this-command 'yank-pop))
  371. (longlines-decode-region (point) (mark t))
  372. (if longlines-showing
  373. (longlines-show-region (point) (mark t))))
  374. (longlines-showing
  375. (cond ((eq this-command 'newline)
  376. (save-excursion
  377. (if (search-backward "\n" nil t)
  378. (longlines-show-region
  379. (match-beginning 0) (match-end 0)))))
  380. ((eq this-command 'open-line)
  381. (save-excursion
  382. (if (search-forward "\n" nil t)
  383. (longlines-show-region
  384. (match-beginning 0) (match-end 0))))))))
  385. (unless (or (eq this-command 'fill-paragraph)
  386. (eq this-command 'fill-region))
  387. (longlines-wrap-region longlines-wrap-beg longlines-wrap-end))
  388. (setq longlines-wrap-beg nil)
  389. (setq longlines-wrap-end nil)))
  390. (defun longlines-window-change-function ()
  391. "Re-wrap the buffer if the window width has changed.
  392. This is called by `window-size-change-functions'."
  393. (when (/= fill-column (- (window-width) window-min-width))
  394. (setq fill-column (- (window-width) window-min-width))
  395. (let ((mod (buffer-modified-p)))
  396. (longlines-wrap-region (point-min) (point-max))
  397. (set-buffer-modified-p mod))))
  398. ;; Advice for editing functions
  399. (defadvice kill-region (before longlines-encode-kill activate)
  400. "If the buffer is in `longlines-mode', remove all soft newlines."
  401. (when longlines-mode
  402. (longlines-encode-region (ad-get-arg 0) (ad-get-arg 1))))
  403. (defadvice copy-region-as-kill (around longlines-encode-kill activate)
  404. "If the buffer is in `longlines-mode', remove all soft newlines."
  405. (if longlines-mode
  406. (let ((string (buffer-substring beg end)))
  407. (with-temp-buffer
  408. (insert string)
  409. (longlines-encode-region (point-min) (point-max))
  410. (ad-set-arg 0 (point-min))
  411. (ad-set-arg 1 (point-max))
  412. ad-do-it))
  413. ad-do-it))
  414. ;; Loading and saving
  415. (defun longlines-before-revert-hook ()
  416. (add-hook 'after-revert-hook 'longlines-after-revert-hook nil t)
  417. (longlines-mode 0))
  418. (defun longlines-after-revert-hook ()
  419. (remove-hook 'after-revert-hook 'longlines-after-revert-hook t)
  420. (longlines-mode 1))
  421. (add-to-list
  422. 'format-alist
  423. (list 'longlines "Automatically wrap long lines." nil nil
  424. 'longlines-encode-region t nil))
  425. (provide 'longlines)
  426. ;;; longlines.el ends here