summaryrefslogtreecommitdiffstats
path: root/emacs.d/nxhtml/util/fold-dwim.el
blob: 11b3a3dcda6027dc48dd4abe28facd71531edc4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
;;; fold-dwim.el -- Unified user interface for Emacs folding modes
;;
;; Copyright (C) 2004 P J Heslin
;;
;; Author: Peter Heslin <p.j.heslin@dur.ac.uk>
;; 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 "<f7>")      'fold-dwim-toggle)
;;  (global-set-key (kbd "<M-f7>")    'fold-dwim-hide-all)
;;  (global-set-key (kbd "<S-M-f7>")  '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)