;;; fold-dwim.el -- Unified user interface for Emacs folding modes ;; ;; Copyright (C) 2004 P J Heslin ;; ;; Author: Peter Heslin ;; URL: http://www.dur.ac.uk/p.j.heslin/Software/Emacs/Download/fold-dwim.el (defconst fold-dwim:version "1.4") ;; ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; If you do not have a copy of the GNU General Public License, you ;; can obtain one by writing to the Free Software Foundation, Inc., 59 ;; Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;;; Overview: ;; ;; DWIM stands for "do what I mean", as in the idea that one keystroke ;; can do different things depending on the context. In this package, ;; it means that, if the cursor is in a currently hidden folded ;; construction, we want to show it; if it's not, we want to hide ;; whatever fold the cursor is in. ;; ;; Some editors other than Emacs provide a single mechanism for ;; folding text which various file types can exploit. The advantage ;; of this arrangement is that the user only has to know one set of ;; folding commands; the disadvantage is that the various file types ;; are limited to using whatever functionality is provided centrally. ;; Emacs by contrast provides a very general and powerful framework ;; for hiding text, which major modes can use as they see fit. The ;; advantage of this is that each major mode can deal with folding in ;; the way that is suitable for that type of file; the disadvantage is ;; that different major modes have different styles of folding, and ;; provide different key bindings. ;; ;; In practice, matters are simpler than that, since most major modes ;; delegate the task of folding to packages like outline.el and ;; hideshow.el. The key bindings for these two packages alone, ;; however, are numerous and for some people hard to type. Another ;; usability complication arises when a package like AucTeX uses ;; outline-minor-mode for some folds, and provides its own ;; key-bindings for other kinds of folds. Likewise, nXML-mode ;; provides its own style of folding for certain types of files, but ;; for files that don't fit that paradigm (such as XHTML), you may ;; want to use outline-minor-mode instead. ;; ;; The goal of this package is to reduce this complexity to three ;; globally-defined keystrokes: one to toggle the state of the fold at ;; point, whatever its type may be, one to hide all folds of all types ;; in the buffer, and one to show all folds. ;; ;; This package currently knows about folding-mode (from folding.el), ;; hs-minor-mode (from hideshow.el), outline-minor-mode (from ;; outline.el), TeX-fold-mode (from AUCTeX), and nXML-mode outlining. ;; More could be added. It is not necessary to have folding.el, ;; AUCTeX or nXML-mode installed, if you just want to use it with the ;; built-in modes. ;;; Usage: ;; ;; You will need to have one or more of following minor modes switched ;; on: hs-minor-mode, outline-minor-mode, TeX-fold-mode, folding-mode. ;; Otherwise no folds may be found. There are three functions to try: ;; ;; fold-dwim-toggle: try to show any hidden text at the cursor; if no ;; hidden text is found, try to hide the text at the cursor. ;; ;; fold-dwim-hide-all: hide all folds in the buffer. ;; ;; fold-dwim-show-all: show all folds in the buffer. ;;; Configuration ;; ;; This package binds no keys by default, so you need to find three ;; free and convenient key-bindings. This is what I use: ;; ;; (global-set-key (kbd "") 'fold-dwim-toggle) ;; (global-set-key (kbd "") 'fold-dwim-hide-all) ;; (global-set-key (kbd "") 'fold-dwim-show-all) ;; ;;; Advanced Configuration ;; ;; With respect to outline-minor-mode (or outline-mode), dwim-fold ;; provides two different styles of usage. The first is a "nested" ;; style which only shows top-level headings when you fold the whole ;; buffer, and then allows you to drill down progressively through the ;; other levels. The other is a "flat" style, whereby folding the ;; entire buffer shows all headings at every level. ;; ;; The default is "flat", but if you want to change the default, you ;; can set the value of fold-dwim-outline-style-default to be 'flat or ;; 'nested. If you wish to override the default for a particular ;; major mode, put a value of either 'flat or 'nested for the ;; fold-dwim-outline-style property of the major-mode symbol, like so: ;; ;; (put 'org-mode 'fold-dwim-outline-style 'nested) ;; ;; At present, there is no way to customize nXML-mode outlining to use ;; the nested style, since it is not really supported by that mode ;; (there is no function to hide all text and subheadings in the ;; buffer). ;;; Compatibility ;; ;; Tested with GNU Emacs CVS (from Sept. 10, 2004), AUCTeX version ;; 11.53, nxml-mode version 20041004, folding.el version 2.97. ;; ;; If there are any other important major or minor modes that do ;; folding and that could usefully be handled in this package, please ;; let me know. ;;; Bugs ;; ;; It is possible that some of the various folding modes may interact ;; badly if used together; I have not tested all permutations. ;; ;; The function fold-dwim-hide tries various folding modes in ;; succession, and stops when it finds one that successfully makes a ;; fold at point. This means that the order in which those modes are ;; tried is significant. I have not spent a lot of time thinking ;; about what the optimal order would be; all I care about is that ;; hideshow and TeX-fold have priority over outline-minor-mode (since ;; for me they usually fold smaller chunks of the file). ;; ;; I don't use folding.el myself, so that functionality is not well ;; tested. ;;; Changes ;; ;; 1.0 Initial release ;; 1.1 Bugfix: test if folding-mode is bound ;; 1.2 fold-dwim-hide-all and -show-all operate only on active region ;; in transient-mark-mode. ;; 1.3 Added outline-mode (Lennart Borgman) ;; 1.4 Removed nxml-mode style folding (Lennart Borgman) ;; + some functions used by nXhtml. (require 'outline) (require 'hideshow) ;;;###autoload (defgroup fold-dwim nil "Unified interface to folding commands" :prefix "fold-dwim-" :group 'editing) (defcustom fold-dwim-outline-style-default 'flat "Default style in which to fold in outline-minor-mode: 'nested or 'flat." :type '(choice (const :tag "Flat (show all headings)" flat) (const :tag "Nested (nest headings hierarchically)" nested)) :group 'fold-dwim) (defvar fold-dwim-toggle-selective-display 'nil "Set this non-nil to make fold-dwim functions use selective display (folding of all lines indented as much or more than the current line). Probably only useful for minor modes like makefile-mode that don't provide a more intelligent way of folding.") (make-variable-buffer-local 'fold-dwim-toggle-selective-display) (defun fold-dwim-maybe-recenter () "It's annoyingly frequent that hiding a fold will leave you with point on the top or bottom line of the screen, looking at nothing but an ellipsis. TODO: only recenter if we end up near the top or bottom of the screen" (recenter)) (defun fold-dwim-toggle-selective-display () "Set selective display to indentation of current line" (interactive) (if (numberp selective-display) (set-selective-display nil) (save-excursion (beginning-of-line) (skip-chars-forward " \t") (let ((col (current-column))) (if (zerop col) (set-selective-display nil) (set-selective-display col)))))) (defun fold-dwim-hide-all () "Hide all folds of various kinds in the buffer or region" (interactive) (save-excursion (save-restriction (when (and transient-mark-mode mark-active) (narrow-to-region (region-beginning) (region-end))) (when (and (boundp 'TeX-fold-mode) TeX-fold-mode) (TeX-fold-buffer)) (when hs-minor-mode (hs-hide-all)) (when (or outline-minor-mode (eq major-mode 'outline-mode)) (if (fold-dwim-outline-nested-p) (hide-sublevels 1) (hide-body))) ;; (when (derived-mode-p 'nxml-mode) ;; (nxml-hide-all-text-content)) (when (and (boundp 'folding-mode) folding-mode) (folding-whole-buffer)))) (fold-dwim-maybe-recenter)) (defun fold-dwim-show-all () "Show all folds of various kinds in the buffer or region" (interactive) (save-excursion (save-restriction (when (and transient-mark-mode mark-active) (narrow-to-region (region-beginning) (region-end))) (when (and (boundp 'TeX-fold-mode) TeX-fold-mode) (TeX-fold-clearout-buffer)) (when hs-minor-mode (hs-show-all)) ;; (when (derived-mode-p 'nxml-mode) ;; (nxml-show-all)) (when (or outline-minor-mode (eq major-mode 'outline-mode)) (show-all)) (when (and (boundp 'folding-mode) folding-mode) (folding-open-buffer)) (when fold-dwim-toggle-selective-display (set-selective-display 'nil))))) (defun fold-dwim-hide () "Hide one item" (or (and (boundp 'TeX-fold-mode) TeX-fold-mode (let ((type (fold-dwim-auctex-env-or-macro))) (when type (TeX-fold-item type)))) ;; Look for html headers. (when (and (derived-mode-p 'nxml-mode 'html-mode) outline-minor-mode) (when (save-excursion (save-match-data (looking-back (rx "<" (optional "/") "h" (any "1-6") (0+ (not (any "<"))))))) (hide-entry) t)) (and hs-minor-mode (when (save-excursion (or (hs-find-block-beginning) (hs-inside-comment-p))) (hs-hide-block) (hs-already-hidden-p))) ;; (and (derived-mode-p 'nxml-mode) ;; (condition-case nil ;; (save-excursion ;; (nxml-back-to-section-start)) ;; (error nil)) ;; (nxml-hide-text-content)) (and (boundp 'folding-mode) folding-mode (condition-case nil (save-excursion (folding-hide-current-entry) t) (error nil))) (when (or outline-minor-mode (eq major-mode 'outline-mode)) (if (fold-dwim-outline-nested-p) (hide-subtree) (hide-entry)))) (fold-dwim-maybe-recenter)) (defun fold-dwim-show () "If point is in a closed or temporarily open fold, open it. Returns nil if nothing was done" (save-excursion (let ((stop)) (when (and (or outline-minor-mode (eq major-mode 'outline-mode)) (or (fold-dwim-outline-invisible-p (line-end-position)) (and (bolp) (not (bobp)) (fold-dwim-outline-invisible-p (1- (point)))))) (if (not (fold-dwim-outline-nested-p)) (show-entry) (show-children) (show-entry)) (setq stop "outline-minor-mode")) (when (and (not stop) hs-minor-mode (hs-already-hidden-p)) (hs-show-block) (setq stop "hs-minor-mode")) (when (and (not stop) (boundp 'TeX-fold-mode) TeX-fold-mode) (let ((overlays (overlays-at (point)))) (while overlays (when (eq (overlay-get (car overlays) 'category) 'TeX-fold) (delete-overlay (car overlays)) (setq stop "Tex-fold-mode")) (setq overlays (cdr overlays))))) ;; (when (and (not stop) ;; (derived-mode-p 'nxml-mode)) ;; (let ((overlays (overlays-at (point)))) ;; (while (and overlays (not stop)) ;; (when (overlay-get (car overlays) 'nxml-outline-display) ;; (setq stop "nxml folding")) ;; (setq overlays (cdr overlays)))) ;; (when stop ;; (nxml-show))) (when (and (not stop) (boundp 'folding-mode) folding-mode (save-excursion (beginning-of-line) (let ((current-line-mark (folding-mark-look-at))) (when (and (numberp current-line-mark) (= current-line-mark 0)) (folding-show-current-entry) (setq stop "folding-mode")))))) stop))) ;;;###autoload (defun fold-dwim-toggle () "Toggle visibility or some other visual things. Try toggling different visual things in this order: - Images shown at point with `inlimg-mode' - Text at point prettified by `html-write-mode'. For the rest it unhides if possible, otherwise hides in this order: - `org-mode' header or something else using that outlines. - Maybe `fold-dwim-toggle-selective-display'. - `Tex-fold-mode' things. - In html if `outline-minor-mode' and after heading hide content. - `hs-minor-mode' things. - `outline-minor-mode' things. (Turns maybe on this.) It uses `fold-dwim-show' to show any hidden text at point; if no hidden fold is found, try `fold-dwim-hide' to hide the construction at the cursor. Note: Also first turn on `fold-dwim-mode' to get the keybinding for this function from it." (interactive) (fold-dwim-mode 1) (cond ((get-char-property (point) 'html-write) (html-write-toggle-current-tag)) ((get-char-property (point) 'inlimg-img) (inlimg-toggle-display (point))) ((eq major-mode 'org-mode) (org-cycle)) ((and (fboundp 'outline-cycle) outline-minor-mode) (outline-cycle)) (t (unless (or outline-minor-mode hs-minor-mode) (outline-minor-mode 1)) (if fold-dwim-toggle-selective-display (fold-dwim-toggle-selective-display) (let ((unfolded (fold-dwim-show))) (if unfolded (message "Fold DWIM showed: %s" unfolded) (fold-dwim-hide))))))) ;;;###autoload (define-minor-mode fold-dwim-mode "Key binding for `fold-dwim-toggle'." :global t :group 'nxhtml :group 'foldit nil) ;; Fix-me: Maybe move to fold-dwim and rethink? (defvar fold-dwim-mode-map (let ((map (make-sparse-keymap))) (define-key map [(control ?c) ?+] 'fold-dwim-toggle) map)) ;;;###autoload (defun fold-dwim-unhide-hs-and-outline () "Unhide everything hidden by Hide/Show and Outline. Ie everything hidden by `hs-minor-mode' and `outline-minor-mode'." (interactive) (hs-show-all) (show-all)) ;;;###autoload (defun fold-dwim-turn-on-hs-and-hide () "Turn on minor mode `hs-minor-mode' and hide. If major mode is derived from `nxml-mode' call `hs-hide-block' else call `hs-hide-all'." (interactive) (hs-minor-mode 1) (foldit-mode 1) (if (derived-mode-p 'nxml-mode) (hs-hide-block) (hs-hide-all))) ;;;###autoload (defun fold-dwim-turn-on-outline-and-hide-all () "Turn on `outline-minor-mode' and call `hide-body'." (interactive) (outline-minor-mode 1) (foldit-mode 1) (hide-body)) (defun fold-dwim-auctex-env-or-macro () (let ((type (cond ;; Fold macro before env, unless it's begin or end ((save-excursion (let ((macro-start (TeX-find-macro-start))) (and macro-start (not (= macro-start (point))) (goto-char macro-start) (not (looking-at (concat (regexp-quote TeX-esc) "\\(begin\\|end\\)[ \t]*{")))))) 'macro) ((and (eq major-mode 'context-mode) (save-excursion (ConTeXt-find-matching-start) (point))) 'env) ((and (eq major-mode 'texinfo-mode) (save-excursion (Texinfo-find-env-start) (point))) 'env) ((and (eq major-mode 'latex-mode) (condition-case nil (save-excursion (LaTeX-find-matching-begin) (point) (not (looking-at "\\\\begin[ \t]*{document}"))) (error nil))) 'env) (t nil)))) type)) (defun fold-dwim-outline-invisible-p (pos) "The version of this function in outline.el doesn't work so well for our purposes, because it doesn't distinguish between invisibility caused by outline, and that of other modes." (save-excursion (goto-char pos) (let ((overlays (overlays-at (point))) (found-one)) (while overlays (when (eq (overlay-get (car overlays) 'invisible) 'outline) (setq found-one t)) (setq overlays (cdr overlays))) found-one))) (defun fold-dwim-outline-nested-p () "Are we using the flat or nested style for outline-minor-mode?" (let ((style (get major-mode 'fold-dwim-outline-style))) (if style (eq style 'nested) (eq fold-dwim-outline-style-default 'nested)))) (provide 'fold-dwim)