;;; By Joseph Szabo 10/31/96 ;;; Minor mode for word wrapping like typical word processors (sort of) ;;; The newest version is always at ;;; http://nbcs.rutgers.edu/~jszabo/wrap-mode.el ;;; my email is jszabo@nbcs.rutgers.edu ;;; It's like auto-fill-mode, but it calls fill-paragraph every time ;;; text changes. So it's better for reediting of typed text, ;;; inserting and deleting. ;;; I highly recommend getting filladapt.el too. It redefines ;;; fill-paragraph and do-auto-fill. See also my-auto-fill.el. These ;;; are all on my nbcs homepage. ;;; Quick Installation in .emacs: ; (autoload 'wrap-mode "~/wrap-mode.el" "Toggle wrapping." t) ;;; Then type esc x wrap-mode to toggle it on or off ;;;; ChangeLog: ;;; 11/29/96: disabled saving undo info while wrapping, it was getting ;;; in the way of delete, then yank back ;;; 11/26/96: Quick fix for problem where control w cuts text but ;;; control y doesn't yank it back. Hmm, is ;;; after-change-functions called while a lisp function is ;;; still running, like kill-region? Yes, and the undo ;;; list gets altered by fill-paragraph, then it's too late ;;; for kill-region to copy what it's killed from the ;;; undo-list. This is probably a problem with all delete ;;; functions. ;;; 11/19/96: Added suggestion to ignore In-reply-to: in replying mail headers ;;; 11/16/96: Added wrap-mode-hook ;;; 11/15/96: Made it work well with the new use-hard-newlines ;;; variable (which is always set to t in wrap-mode). ;;;; FAQ: ;;; How can I ignore lines that start with \ in tex-mode? ;;; Use wrap-inhibit-regexp, see also the mail-mode example farther down ;(add-hook 'tex-mode-hook ; '(lambda () ; (setq wrap-inhibit-regexp "[ \\t]*\\\\"))) ;;; How do I start and stop it interactively? ;;; After you change your .emacs like below and start emacs again, type ;;; esc x wrap ;;; Are there any pitfalls? ;;; esc x undo and esc x fill-paragraph will get you into a loop, but using ;;; control x u control _ and esc q will work fine. If you get into a loop, ;;; just press control g. ;;; Hitting r in rmail mode will also take a long time, unless you ;;; ignore all the headers for wrapping, like in the example for ;;; mail-mode below. ;;; This sounds like auto-fill-mode. How is it different? ;;; Auto-fill-mode breaks the current line (calls indent-new-comment-line) at ;;; the appropriate place when you press return or the space-bar on a column ;;; past the fill-column. ;;; Wrap-mode re-fills the current paragraph (calls fill-paragraph) under these ;;; conditions (much like a word processor): ;;; Whenever the current line is over fill-column characters, no matter where ;;; the point is, after any insertion or deletion ;;; Whenever you delete some characters and the first word on the next line (in ;;; the same paragraph) can fit on the current line ;;; Whenever the first word on the current line can fit on the previous line ;;; (in the same paragraph). This can happen after you delete some ;;; characters in the first word or if you press space in the middle of the ;;; first word (thorough of me no?) ;;; Basically whenever the current line inside a paragraph gets too long or ;;; short and you want to press esc q to fix it. ;;; It's ideal for writing email and html code (at least the way I do ;;; it), and maybe tex (but I don't know tex). It should work well ;;; wherever you type esc q often. ;;; It's not as good for writing programming code, because esc q tends to mush ;;; all the code together, except it works nicely on comments. ;;; Works great with the filladapt.el package. ;;; (ftp://archive.cis.ohio-state.edu/pub/gnu/emacs/elisp-archive/packages/filladapt.el.Z) ;;;; Installation: ;;; Here's how my .emacs uses this, sort of (without ';' in front of commands): ;;; this will let you interactively type esc x wrap to start and stop ;;; wrap-mode, assuming wrap-mode.el is saved somewhere in load-path ; (autoload 'wrap-mode "wrap-mode" "Toggle word wrap." t) ;;; this makes it easy to pass wrap-mode to various hooks ; (autoload 'turn-on-wrap "wrap-mode" "Turn on word wrap always." t) ;;; use wrapping with mail mode, without the headers messing up ;(add-hook 'mail-mode-hook ; '(lambda () ; (wrap-mode 1) ; ;; after regexp is local ; (setq wrap-inhibit-regexp ; "To:\\|CC:\\|FCC:\\|BCC:\\|Subject:\\|In-reply-to:"))) ;;; use wrapping with text-mode, (other modes call text-mode-hook too, ;;; like mail-mode) ;(add-hook 'text-mode-hook 'turn-on-wrap) ;;; which column to wrap at (default is 70), I like to squeeze it in ; (setq default-fill-column 79) ;;; use text mode (with wrap hooked on it) with .txt files ; (setq auto-mode-alist ; (cons '("\\.txt$" . text-mode) auto-mode-alist)) ;;; use text/wrap mode with pine ; (setq auto-mode-alist ; (cons '("pico\\.[0-9]*" . text-mode) auto-mode-alist)) ;;; use text/wrap mode when editing nn articles ; (setq auto-mode-alist ; (cons '("/tmp/nn\\.[^/\\.]*$" . text-mode) auto-mode-alist)) ;;; text/wrap mode with zmail ; (setq auto-mode-alist ; (cons '("\\.ed[0-9]*[a-z]*" . text-mode) auto-mode-alist)) ;;;; future ideas: ;;; Fix bug where kill-region cuts but yank doesn't bring text back. ;;; work better with fill prefix's like in tex mode, maybe change word-length ;;; get what esc q is bound to before rebinding it to fill-paragraph ;;; (e.g. emacs-lisp mode) ;;; fill only from previous line to paragraph end? ;;; fill-paragraph wish-list: ;;; not add extra carriage-returns (defvar wrap-debug nil "*Whether or not to show some helpful debugging messages.") (defvar wrap-function 'my-wrap-function "*Which function to use for wrapping.") (defconst wrap-inhibit-regexp nil "*Regexp to match lines which should not be wrapped.") (defvar wrap-mode-hook nil "*Functions to be run after wrap-mode is loaded.") (defvar wrap-mode nil "Status of wrap-mode") ;;; register as minor mode (if (not (assq 'wrap-mode minor-mode-alist)) (setq minor-mode-alist (cons '(wrap-mode " Wrap") minor-mode-alist))) ;;; key bindings (defvar wrap-mode-map nil "Local keymap for wrap mode buffers.") ;;; this is to prevent a lot of overhead when after-change-functions is set ;;; and either fill-paragraph or undo are called with their keys (if wrap-mode-map nil (setq wrap-mode-map (make-sparse-keymap)) (define-key wrap-mode-map "\eq" 'safe-fill) (define-key wrap-mode-map "\C-xu" 'safe-undo) (define-key wrap-mode-map [?\C-/] 'safe-undo) ;; This works, but when you disable wrap it doesn't show the key ;; binding in the menu anymore ; (define-key wrap-mode-map [menu-bar edit undo] ; '("Safe Undo" . safe-undo)) (define-key wrap-mode-map "\C-_" 'safe-undo)) ;;; I didn't see an example of minor mode keymaps anywhere. ;;; This took a day to figure out how to say something like: ;;; (setq alist (cons '('wrap-mode . wrap-mode-map) alist)) (if (not (assq 'wrap-mode minor-mode-map-alist)) (setq minor-mode-map-alist (cons (cons 'wrap-mode wrap-mode-map) minor-mode-map-alist))) (defun my-wrap-function nil "Just calls fill-paragraph with a nil argument. This function is the default value of wrap-function." (fill-paragraph nil)) (defun wrap-mode-line nil "Stick ^x^c in the mode line or take it out." (interactive) (if (not (member "^X^C Exit" mode-line-format)) (setq mode-line-format (cons "^X^C Exit" mode-line-format)) (setq mode-line-format (delete "^X^C Exit" mode-line-format)))) (defun safe-fill () "Do fill-paragraph (esc q) safely." (interactive) (message "Safe fill") (let ((after-change-functions (delete 'do-wrap after-change-functions))) (fill-paragraph nil))) ;;; maybe cover menu too (defun safe-undo () "Do undo (control x u, control _) safely." (interactive) (let ((after-change-functions (delete 'do-wrap after-change-functions))) (undo)) (message "Safe undo!")) (defun line-length (line) "Return length of line 'line'. line is relative to the current one." (save-excursion (if (not (zerop line)) (next-line line)) (end-of-line) (let (linelength) (setq linelength (current-column)) ; (message "line length: %d" linelength) linelength))) (defun first-line-of-paragraph-p nil "True if on first line of paragraph." ;;; If the current line is a paragraph start, or we on the first line ;;; of the buffer, or previous newline has the property 'hard', or the ;;; previous line is a paragraph separator, return true. ;;; Also true on paragraph separators. (save-excursion (beginning-of-line) (or (save-match-data (looking-at paragraph-start)) (bobp) (get-text-property (1- (point)) 'hard) (progn (previous-line 1) (save-match-data (looking-at paragraph-separate)))))) (defun last-line-of-paragraph-p nil "True if on last line of paragraph." ;;; If we on the last line of the buffer, or the next newline is hard, ;;; or the next line is a paragraph separator, return true. (save-excursion (end-of-line) (or (eobp) (get-text-property (point) 'hard) (progn (next-line 1) (beginning-of-line) (save-match-data (looking-at paragraph-separate)))))) ;;; need to jump to next space instead of forward-word (defun word-length (line pos) "Return length of word on line 'line' and word position pos. line is relative to the current one. Words are only divided by spaces or ends of line (including end of buffer)." ;; maybe I should temporarily change syntax and use forward-word (save-excursion (beginning-of-line) (next-line line) (if (> pos 1) (re-search-forward " \\|$" nil nil (1- pos))) ;just space or line end (let ((beg (point)) (end nil)) (re-search-forward " \\|$") (setq end (point)) (if (eolp) (- end beg) (1- (- end beg)))))) (defun do-wrap (beg end deleted-chars) "Function to pass to the after-change-functions variable via wrap-function (after it's made local). It does the work of filling the current paragraph whenever some line in the paragraph is too long or small." ;;; wrap conditions (try to make more efficient): ;;; if inhibit expression is nil or not looking at inhibit exp and: ;;; if line too long (inserting or deleting, deleting is rare though) ;;; else if inserting and ;;; space makes first word able to fit on previous line (minor) ;;; else if deleting and ;;; first word from second line can fit on current line or ;;; (minor) first word on current line can fit on previous line" (let ((linelength (line-length 0))) (if ;;; both inhibit expr and all other conditions (and (or (not wrap-inhibit-regexp) (not (save-excursion (beginning-of-line) (save-match-data (looking-at wrap-inhibit-regexp))))) (cond ;; if line too long, fill (both inserting and deleting) ;; and not at fill column with a space ((and (> linelength fill-column) (not (and (equal (preceding-char) ?\ ) (last-line-of-paragraph-p))) ;; message seems to always have a true value (if wrap-debug (message "line too long") t))) ((zerop deleted-chars) ; we are inserting ;; if made first word shorter by inserting a space, fill ;; can make some local variables here (cond ((equal (preceding-char) ?\ ) (and ;; getting line-length twice (not (first-line-of-paragraph-p)) (not (last-line-of-paragraph-p)) (< (+ (line-length -1) (word-length 0 1)) fill-column) (if wrap-debug (message "first word can go on previous line after sp") t))))) ;; if have deleted and word from next line can fit, fill (t ; these are all deletion cases (or (and (not (last-line-of-paragraph-p)) (< (+ linelength (word-length 1 1)) fill-column) ;; we are on a blank line or other separator (save-match-data (not (looking-at paragraph-separate))) (if wrap-debug (message "1st word on next line can go on this line - del") t)) ;; if first word can fit on previous line (which isn't blank), ;; after delete, fill some tests might be done already (and (not (first-line-of-paragraph-p)) ;; need +1 for a space (< (+ (line-length -1) (word-length 0 1)) fill-column) (save-match-data (not (looking-at paragraph-separate))) (if wrap-debug (message "first word can fit on previous line after del") t)))))) (let ((buffer-undo-list t)) (funcall wrap-function))))) ;;; start word wrapping minor mode ;; just put two keys instead maybe (defun wrap-mode (&optional arg) "Do word-wrapping almost like a word processor. esc x wrap-mode toggles it on or off. Great for writing email. Paragraphs are divided by paragraph-separate, or start with paragraph-start, or end with hard newlines (use-hard-newlines is always set). Usually paragraphs are separated by blank lines, or their first lines are indented. Special commands: \\\\{wrap-mode-map} Variables: wrap-inhibit-regexp can be set to inhibit wrapping on lines that begin with certain regular expressions. wrap-function is the function called everytime a wrap is required. Default is 'my-fill-paragraph. wrap-debug, if set to t, will give a more verbose output when it fills (it will tell you why at the bottom) wrap-mode-hook: list of functions to run after wrap-mode is started By Joseph Szabo." (interactive "P") (make-local-variable 'wrap-mode) (make-local-variable 'after-change-functions) ;; I think I'm going about this wrong; hmm, it's ok if you set these ;; after wrap-mode is run (make-local-variable 'wrap-function) (make-local-variable 'wrap-inhibit-regexp) ;; Your version of emacs may have this. Manual carriage return marks ;; new paragraph. If you don't have it, it won't matter. (if (boundp 'use-hard-newlines) (setq use-hard-newlines t)) (setq wrap-mode (if (null arg) (not wrap-mode) (> (prefix-numeric-value arg) 0))) (cond (wrap-mode (setq after-change-functions (cons 'do-wrap after-change-functions)) (run-hooks 'wrap-mode-hook)) (t ; else turn it off (setq after-change-functions (delete 'do-wrap after-change-functions))))) (defun turn-on-wrap nil "Turn on wrap-mode always." (interactive) (wrap-mode 1)) ;; docstring (defun wrap-function () "Function used to wrap lines." nil)