1 ;;; fold-dwim.el -- Unified user interface for Emacs folding modes
3 ;; Copyright (C) 2004 P J Heslin
5 ;; Author: Peter Heslin <p.j.heslin@dur.ac.uk>
6 ;; URL: http://www.dur.ac.uk/p.j.heslin/Software/Emacs/Download/fold-dwim.el
7 (defconst fold-dwim:version "1.4")
9 ;; This program 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)
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; If you do not have a copy of the GNU General Public License, you
20 ;; can obtain one by writing to the Free Software Foundation, Inc., 59
21 ;; Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 ;; DWIM stands for "do what I mean", as in the idea that one keystroke
26 ;; can do different things depending on the context. In this package,
27 ;; it means that, if the cursor is in a currently hidden folded
28 ;; construction, we want to show it; if it's not, we want to hide
29 ;; whatever fold the cursor is in.
31 ;; Some editors other than Emacs provide a single mechanism for
32 ;; folding text which various file types can exploit. The advantage
33 ;; of this arrangement is that the user only has to know one set of
34 ;; folding commands; the disadvantage is that the various file types
35 ;; are limited to using whatever functionality is provided centrally.
36 ;; Emacs by contrast provides a very general and powerful framework
37 ;; for hiding text, which major modes can use as they see fit. The
38 ;; advantage of this is that each major mode can deal with folding in
39 ;; the way that is suitable for that type of file; the disadvantage is
40 ;; that different major modes have different styles of folding, and
41 ;; provide different key bindings.
43 ;; In practice, matters are simpler than that, since most major modes
44 ;; delegate the task of folding to packages like outline.el and
45 ;; hideshow.el. The key bindings for these two packages alone,
46 ;; however, are numerous and for some people hard to type. Another
47 ;; usability complication arises when a package like AucTeX uses
48 ;; outline-minor-mode for some folds, and provides its own
49 ;; key-bindings for other kinds of folds. Likewise, nXML-mode
50 ;; provides its own style of folding for certain types of files, but
51 ;; for files that don't fit that paradigm (such as XHTML), you may
52 ;; want to use outline-minor-mode instead.
54 ;; The goal of this package is to reduce this complexity to three
55 ;; globally-defined keystrokes: one to toggle the state of the fold at
56 ;; point, whatever its type may be, one to hide all folds of all types
57 ;; in the buffer, and one to show all folds.
59 ;; This package currently knows about folding-mode (from folding.el),
60 ;; hs-minor-mode (from hideshow.el), outline-minor-mode (from
61 ;; outline.el), TeX-fold-mode (from AUCTeX), and nXML-mode outlining.
62 ;; More could be added. It is not necessary to have folding.el,
63 ;; AUCTeX or nXML-mode installed, if you just want to use it with the
68 ;; You will need to have one or more of following minor modes switched
69 ;; on: hs-minor-mode, outline-minor-mode, TeX-fold-mode, folding-mode.
70 ;; Otherwise no folds may be found. There are three functions to try:
72 ;; fold-dwim-toggle: try to show any hidden text at the cursor; if no
73 ;; hidden text is found, try to hide the text at the cursor.
75 ;; fold-dwim-hide-all: hide all folds in the buffer.
77 ;; fold-dwim-show-all: show all folds in the buffer.
81 ;; This package binds no keys by default, so you need to find three
82 ;; free and convenient key-bindings. This is what I use:
84 ;; (global-set-key (kbd "<f7>") 'fold-dwim-toggle)
85 ;; (global-set-key (kbd "<M-f7>") 'fold-dwim-hide-all)
86 ;; (global-set-key (kbd "<S-M-f7>") 'fold-dwim-show-all)
89 ;;; Advanced Configuration
91 ;; With respect to outline-minor-mode (or outline-mode), dwim-fold
92 ;; provides two different styles of usage. The first is a "nested"
93 ;; style which only shows top-level headings when you fold the whole
94 ;; buffer, and then allows you to drill down progressively through the
95 ;; other levels. The other is a "flat" style, whereby folding the
96 ;; entire buffer shows all headings at every level.
98 ;; The default is "flat", but if you want to change the default, you
99 ;; can set the value of fold-dwim-outline-style-default to be 'flat or
100 ;; 'nested. If you wish to override the default for a particular
101 ;; major mode, put a value of either 'flat or 'nested for the
102 ;; fold-dwim-outline-style property of the major-mode symbol, like so:
104 ;; (put 'org-mode 'fold-dwim-outline-style 'nested)
106 ;; At present, there is no way to customize nXML-mode outlining to use
107 ;; the nested style, since it is not really supported by that mode
108 ;; (there is no function to hide all text and subheadings in the
113 ;; Tested with GNU Emacs CVS (from Sept. 10, 2004), AUCTeX version
114 ;; 11.53, nxml-mode version 20041004, folding.el version 2.97.
116 ;; If there are any other important major or minor modes that do
117 ;; folding and that could usefully be handled in this package, please
122 ;; It is possible that some of the various folding modes may interact
123 ;; badly if used together; I have not tested all permutations.
125 ;; The function fold-dwim-hide tries various folding modes in
126 ;; succession, and stops when it finds one that successfully makes a
127 ;; fold at point. This means that the order in which those modes are
128 ;; tried is significant. I have not spent a lot of time thinking
129 ;; about what the optimal order would be; all I care about is that
130 ;; hideshow and TeX-fold have priority over outline-minor-mode (since
131 ;; for me they usually fold smaller chunks of the file).
133 ;; I don't use folding.el myself, so that functionality is not well
138 ;; 1.0 Initial release
139 ;; 1.1 Bugfix: test if folding-mode is bound
140 ;; 1.2 fold-dwim-hide-all and -show-all operate only on active region
141 ;; in transient-mark-mode.
142 ;; 1.3 Added outline-mode (Lennart Borgman)
143 ;; 1.4 Removed nxml-mode style folding (Lennart Borgman)
144 ;; + some functions used by nXhtml.
150 (defgroup fold-dwim nil
151 "Unified interface to folding commands"
155 (defcustom fold-dwim-outline-style-default 'flat
156 "Default style in which to fold in outline-minor-mode: 'nested or
158 :type '(choice (const :tag "Flat (show all headings)" flat)
159 (const :tag "Nested (nest headings hierarchically)" nested))
162 (defvar fold-dwim-toggle-selective-display 'nil
163 "Set this non-nil to make fold-dwim functions use selective
164 display (folding of all lines indented as much or more than the
165 current line). Probably only useful for minor modes like
166 makefile-mode that don't provide a more intelligent way of
169 (make-variable-buffer-local
170 'fold-dwim-toggle-selective-display)
172 (defun fold-dwim-maybe-recenter ()
173 "It's annoyingly frequent that hiding a fold will leave you
174 with point on the top or bottom line of the screen, looking at
175 nothing but an ellipsis. TODO: only recenter if we end up near
176 the top or bottom of the screen"
179 (defun fold-dwim-toggle-selective-display ()
180 "Set selective display to indentation of current line"
182 (if (numberp selective-display)
183 (set-selective-display nil)
186 (skip-chars-forward " \t")
187 (let ((col (current-column)))
189 (set-selective-display nil)
190 (set-selective-display col))))))
192 (defun fold-dwim-hide-all ()
193 "Hide all folds of various kinds in the buffer or region"
197 (when (and transient-mark-mode mark-active)
198 (narrow-to-region (region-beginning) (region-end)))
199 (when (and (boundp 'TeX-fold-mode) TeX-fold-mode)
203 (when (or outline-minor-mode (eq major-mode 'outline-mode))
204 (if (fold-dwim-outline-nested-p)
207 ;; (when (derived-mode-p 'nxml-mode)
208 ;; (nxml-hide-all-text-content))
209 (when (and (boundp 'folding-mode) folding-mode)
210 (folding-whole-buffer))))
211 (fold-dwim-maybe-recenter))
213 (defun fold-dwim-show-all ()
214 "Show all folds of various kinds in the buffer or region"
218 (when (and transient-mark-mode mark-active)
219 (narrow-to-region (region-beginning) (region-end)))
220 (when (and (boundp 'TeX-fold-mode) TeX-fold-mode)
221 (TeX-fold-clearout-buffer))
224 ;; (when (derived-mode-p 'nxml-mode)
226 (when (or outline-minor-mode (eq major-mode 'outline-mode))
228 (when (and (boundp 'folding-mode) folding-mode)
229 (folding-open-buffer))
230 (when fold-dwim-toggle-selective-display
231 (set-selective-display 'nil)))))
233 (defun fold-dwim-hide ()
235 (or (and (boundp 'TeX-fold-mode)
237 (let ((type (fold-dwim-auctex-env-or-macro)))
239 (TeX-fold-item type))))
240 ;; Look for html headers.
241 (when (and (derived-mode-p 'nxml-mode 'html-mode)
243 (when (save-excursion
245 (looking-back (rx "<" (optional "/")
247 (0+ (not (any "<")))))))
251 (when (save-excursion
252 (or (hs-find-block-beginning) (hs-inside-comment-p)))
254 (hs-already-hidden-p)))
255 ;; (and (derived-mode-p 'nxml-mode)
256 ;; (condition-case nil
258 ;; (nxml-back-to-section-start))
260 ;; (nxml-hide-text-content))
261 (and (boundp 'folding-mode)
265 (folding-hide-current-entry)
268 (when (or outline-minor-mode (eq major-mode 'outline-mode))
269 (if (fold-dwim-outline-nested-p)
272 (fold-dwim-maybe-recenter))
275 (defun fold-dwim-show ()
276 "If point is in a closed or temporarily open fold,
277 open it. Returns nil if nothing was done"
280 (when (and (or outline-minor-mode (eq major-mode 'outline-mode))
281 (or (fold-dwim-outline-invisible-p (line-end-position))
284 (fold-dwim-outline-invisible-p (1- (point))))))
285 (if (not (fold-dwim-outline-nested-p))
289 (setq stop "outline-minor-mode"))
290 (when (and (not stop)
292 (hs-already-hidden-p))
294 (setq stop "hs-minor-mode"))
295 (when (and (not stop)
296 (boundp 'TeX-fold-mode)
298 (let ((overlays (overlays-at (point))))
300 (when (eq (overlay-get (car overlays) 'category) 'TeX-fold)
301 (delete-overlay (car overlays))
302 (setq stop "Tex-fold-mode"))
303 (setq overlays (cdr overlays)))))
304 ;; (when (and (not stop)
305 ;; (derived-mode-p 'nxml-mode))
306 ;; (let ((overlays (overlays-at (point))))
307 ;; (while (and overlays (not stop))
308 ;; (when (overlay-get (car overlays) 'nxml-outline-display)
309 ;; (setq stop "nxml folding"))
310 ;; (setq overlays (cdr overlays))))
313 (when (and (not stop)
314 (boundp 'folding-mode)
318 (let ((current-line-mark (folding-mark-look-at)))
319 (when (and (numberp current-line-mark)
320 (= current-line-mark 0))
321 (folding-show-current-entry)
322 (setq stop "folding-mode"))))))
326 (defun fold-dwim-toggle ()
327 "Toggle visibility or some other visual things.
328 Try toggling different visual things in this order:
330 - Images shown at point with `inlimg-mode'
331 - Text at point prettified by `html-write-mode'.
333 For the rest it unhides if possible, otherwise hides in this
336 - `org-mode' header or something else using that outlines.
337 - Maybe `fold-dwim-toggle-selective-display'.
338 - `Tex-fold-mode' things.
339 - In html if `outline-minor-mode' and after heading hide content.
340 - `hs-minor-mode' things.
341 - `outline-minor-mode' things. (Turns maybe on this.)
343 It uses `fold-dwim-show' to show any hidden text at point; if no
344 hidden fold is found, try `fold-dwim-hide' to hide the
345 construction at the cursor.
347 Note: Also first turn on `fold-dwim-mode' to get the keybinding
348 for this function from it."
352 ((get-char-property (point) 'html-write)
353 (html-write-toggle-current-tag))
354 ((get-char-property (point) 'inlimg-img)
355 (inlimg-toggle-display (point)))
356 ((eq major-mode 'org-mode)
358 ((and (fboundp 'outline-cycle)
362 (unless (or outline-minor-mode hs-minor-mode)
363 (outline-minor-mode 1))
364 (if fold-dwim-toggle-selective-display
365 (fold-dwim-toggle-selective-display)
366 (let ((unfolded (fold-dwim-show)))
368 (message "Fold DWIM showed: %s" unfolded)
369 (fold-dwim-hide)))))))
372 (define-minor-mode fold-dwim-mode
373 "Key binding for `fold-dwim-toggle'."
379 ;; Fix-me: Maybe move to fold-dwim and rethink?
380 (defvar fold-dwim-mode-map
381 (let ((map (make-sparse-keymap)))
382 (define-key map [(control ?c) ?+] 'fold-dwim-toggle)
386 (defun fold-dwim-unhide-hs-and-outline ()
387 "Unhide everything hidden by Hide/Show and Outline.
388 Ie everything hidden by `hs-minor-mode' and
389 `outline-minor-mode'."
395 (defun fold-dwim-turn-on-hs-and-hide ()
396 "Turn on minor mode `hs-minor-mode' and hide.
397 If major mode is derived from `nxml-mode' call `hs-hide-block'
398 else call `hs-hide-all'."
402 (if (derived-mode-p 'nxml-mode)
407 (defun fold-dwim-turn-on-outline-and-hide-all ()
408 "Turn on `outline-minor-mode' and call `hide-body'."
410 (outline-minor-mode 1)
414 (defun fold-dwim-auctex-env-or-macro ()
416 ;; Fold macro before env, unless it's begin or end
418 (let ((macro-start (TeX-find-macro-start)))
420 (not (= macro-start (point)))
421 (goto-char macro-start)
423 (concat (regexp-quote TeX-esc)
424 "\\(begin\\|end\\)[ \t]*{"))))))
426 ((and (eq major-mode 'context-mode)
428 (ConTeXt-find-matching-start) (point)))
430 ((and (eq major-mode 'texinfo-mode)
432 (Texinfo-find-env-start) (point)))
434 ((and (eq major-mode 'latex-mode)
437 (LaTeX-find-matching-begin) (point)
438 (not (looking-at "\\\\begin[ \t]*{document}")))
445 (defun fold-dwim-outline-invisible-p (pos)
446 "The version of this function in outline.el doesn't work so
447 well for our purposes, because it doesn't distinguish between
448 invisibility caused by outline, and that of other modes."
451 (let ((overlays (overlays-at (point)))
454 (when (eq (overlay-get (car overlays) 'invisible) 'outline)
456 (setq overlays (cdr overlays)))
459 (defun fold-dwim-outline-nested-p ()
460 "Are we using the flat or nested style for outline-minor-mode?"
461 (let ((style (get major-mode 'fold-dwim-outline-style)))
464 (eq fold-dwim-outline-style-default 'nested))))