Don't break emacs <= 24.3 just yet
[bpt/emacs.git] / lisp / progmodes / hideshow.el
CommitLineData
c38e0c97 1;;; hideshow.el --- minor mode cmds to selectively display code/comment blocks -*- coding: utf-8 -*-
6da7653c 2
ba318903 3;; Copyright (C) 1994-2014 Free Software Foundation, Inc.
b578f267 4
9b4a7800 5;; Author: Thien-Thi Nguyen <ttn@gnu.org>
26a0b399 6;; Dan Nicolaescu <dann@ics.uci.edu>
b7c09257 7;; Keywords: C C++ java lisp tools editing comments blocks hiding outlines
a5b101dc 8;; Maintainer-Version: 5.65.2.2
b578f267
EN
9;; Time-of-Day-Author-Most-Likely-to-be-Recalcitrant: early morning
10
11;; This file is part of GNU Emacs.
12
b1fc2b50 13;; GNU Emacs is free software: you can redistribute it and/or modify
b578f267 14;; it under the terms of the GNU General Public License as published by
b1fc2b50
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
b578f267
EN
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
b1fc2b50 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
b578f267 25
6da7653c
TTN
26;;; Commentary:
27
26a0b399 28;; * Commands provided
aaa114d0 29;;
a23c5037 30;; This file provides Hideshow Minor Mode. When active, nine commands
1a8e83dc 31;; are available, implementing block hiding and showing. They (and their
26a0b399 32;; keybindings) are:
aaa114d0 33;;
60470e65
TTN
34;; hs-hide-block C-c @ C-h
35;; hs-show-block C-c @ C-s
36;; hs-hide-all C-c @ C-M-h
37;; hs-show-all C-c @ C-M-s
38;; hs-hide-level C-c @ C-l
39;; hs-toggle-hiding C-c @ C-c
26d654ec 40;; hs-mouse-toggle-hiding [(shift mouse-2)]
26a0b399 41;; hs-hide-initial-comment-block
b578f267 42;;
26a0b399
TTN
43;; Blocks are defined per mode. In c-mode, c++-mode and java-mode, they
44;; are simply text between curly braces, while in Lisp-ish modes parens
45;; are used. Multi-line comment blocks can also be hidden. Read-only
46;; buffers are not a problem, since hideshow doesn't modify the text.
47;;
48;; The command `M-x hs-minor-mode' toggles the minor mode or sets it
49;; (similar to other minor modes).
b578f267 50
26d654ec
TTN
51;; * Suggested usage
52;;
53;; First make sure hideshow.el is in a directory in your `load-path'.
54;; You can optionally byte-compile it using `M-x byte-compile-file'.
865fe16f 55;; Then, add the following to your init file:
26d654ec
TTN
56;;
57;; (load-library "hideshow")
58;; (add-hook 'X-mode-hook ; other modes similarly
aa7d6700 59;; (lambda () (hs-minor-mode 1)))
26d654ec
TTN
60;;
61;; where X = {emacs-lisp,c,c++,perl,...}. You can also manually toggle
62;; hideshow minor mode by typing `M-x hs-minor-mode'. After hideshow is
63;; activated or deactivated, `hs-minor-mode-hook' is run w/ `run-hooks'.
64;;
65;; Additionally, Joseph Eydelnant writes:
66;; I enjoy your package hideshow.el Ver. 5.24 2001/02/13
67;; a lot and I've been looking for the following functionality:
68;; toggle hide/show all with a single key.
69;; Here are a few lines of code that lets me do just that.
70;;
71;; (defvar my-hs-hide nil "Current state of hideshow for toggling all.")
72;; ;;;###autoload
73;; (defun my-toggle-hideshow-all () "Toggle hideshow all."
74;; (interactive)
75;; (setq my-hs-hide (not my-hs-hide))
76;; (if my-hs-hide
77;; (hs-hide-all)
78;; (hs-show-all)))
79;;
80;; [Your hideshow hacks here!]
81
26a0b399
TTN
82;; * Customization
83;;
84;; You can use `M-x customize-variable' on the following variables:
85;;
9b4a7800
TTN
86;; - hs-hide-comments-when-hiding-all -- self-explanatory!
87;; - hs-hide-all-non-comment-function -- if non-nil, when doing a
88;; `hs-hide-all', this function
89;; is called w/ no arguments
90;; - hs-isearch-open -- what kind of hidden blocks to
26a0b399
TTN
91;; open when doing isearch
92;;
9b4a7800
TTN
93;; Some languages (e.g., Java) are deeply nested, so the normal behavior
94;; of `hs-hide-all' (hiding all but top-level blocks) results in very
95;; little information shown, which is not very useful. You can use the
96;; variable `hs-hide-all-non-comment-function' to implement your idea of
97;; what is more useful. For example, the following code shows the next
98;; nested level in addition to the top-level:
99;;
100;; (defun ttn-hs-hide-level-1 ()
101;; (hs-hide-level 1)
102;; (forward-sexp 1))
103;; (setq hs-hide-all-non-comment-function 'ttn-hs-hide-level-1)
104;;
26a0b399
TTN
105;; Hideshow works w/ incremental search (isearch) by setting the variable
106;; `hs-headline', which is the line of text at the beginning of a hidden
107;; block that contains a match for the search. You can have this show up
108;; in the mode line by modifying the variable `mode-line-format'. For
109;; example, the following code prepends this info to the mode line:
aaa114d0 110;;
26a0b399
TTN
111;; (unless (memq 'hs-headline mode-line-format)
112;; (setq mode-line-format
113;; (append '("-" hs-headline) mode-line-format)))
aaa114d0 114;;
26a0b399 115;; See documentation for `mode-line-format' for more info.
aaa114d0
TTN
116;;
117;; Hooks are run after some commands:
118;;
119;; hs-hide-hook in hs-hide-block, hs-hide-all, hs-hide-level
9b4a7800 120;; hs-show-hook hs-show-block, hs-show-all
aaa114d0 121;;
9b4a7800
TTN
122;; One of `hs-hide-hook' or `hs-show-hook' is run for the toggling
123;; commands when the result of the toggle is to hide or show blocks,
124;; respectively. All hooks are run w/ `run-hooks'. See docs for each
125;; variable or hook for more info.
26a0b399
TTN
126;;
127;; Normally, hideshow tries to determine appropriate values for block
128;; and comment definitions by examining the buffer's major mode. If
129;; there are problems, hideshow will not activate and in that case you
130;; may wish to override hideshow's heuristics by adding an entry to
131;; variable `hs-special-modes-alist'. Packages that use hideshow should
132;; do something like:
133;;
aa7d6700 134;; (add-to-list 'hs-special-modes-alist '(my-mode "{{" "}}" ...))
26a0b399
TTN
135;;
136;; If you have an entry that works particularly well, consider
137;; submitting it for inclusion in hideshow.el. See docstring for
138;; `hs-special-modes-alist' for more info on the entry format.
dfdc1af2
TTN
139;;
140;; See also variable `hs-set-up-overlay' for per-block customization of
141;; appearance or other effects associated with overlays. For example:
142;;
143;; (setq hs-set-up-overlay
144;; (defun my-display-code-line-counts (ov)
145;; (when (eq 'code (overlay-get ov 'hs))
146;; (overlay-put ov 'display
147;; (propertize
148;; (format " ... <%d>"
149;; (count-lines (overlay-start ov)
150;; (overlay-end ov)))
151;; 'face 'font-lock-type-face)))))
b578f267 152
26a0b399
TTN
153;; * Bugs
154;;
155;; (1) Hideshow does not work w/ emacs 18 because emacs 18 lacks the
156;; function `forward-comment' (among other things). If someone
157;; writes this, please send me a copy.
158;;
159;; (2) Sometimes `hs-headline' can become out of sync. To reset, type
26d654ec 160;; `M-x hs-minor-mode' twice (that is, deactivate then re-activate
26a0b399 161;; hideshow).
aaa114d0 162;;
26d654ec 163;; (3) Hideshow 5.x is developed and tested on GNU Emacs 20.7.
26a0b399 164;; XEmacs compatibility may have bitrotted since 4.29.
aaa114d0 165;;
9b4a7800
TTN
166;; (4) Some buffers can't be `byte-compile-file'd properly. This is because
167;; `byte-compile-file' inserts the file to be compiled in a temporary
168;; buffer and switches `normal-mode' on. In the case where you have
169;; `hs-hide-initial-comment-block' in `hs-minor-mode-hook', the hiding of
170;; the initial comment sometimes hides parts of the first statement (seems
171;; to be only in `normal-mode'), so there are unbalanced "(" and ")".
172;;
173;; The workaround is to clear `hs-minor-mode-hook' when byte-compiling:
174;;
175;; (defadvice byte-compile-file (around
176;; byte-compile-file-hideshow-off
177;; act)
178;; (let ((hs-minor-mode-hook nil))
179;; ad-do-it))
26d654ec
TTN
180;;
181;; (5) Hideshow interacts badly with Ediff and `vc-diff'. At the moment, the
182;; suggested workaround is to turn off hideshow entirely, for example:
183;;
26d654ec
TTN
184;; (add-hook 'ediff-prepare-buffer-hook 'turn-off-hideshow)
185;; (add-hook 'vc-before-checkin-hook 'turn-off-hideshow)
186;;
187;; In the case of `vc-diff', here is a less invasive workaround:
188;;
189;; (add-hook 'vc-before-checkin-hook
aa7d6700
TTN
190;; (lambda ()
191;; (goto-char (point-min))
192;; (hs-show-block)))
26d654ec
TTN
193;;
194;; Unfortunately, these workarounds do not restore hideshow state.
195;; If someone figures out a better way, please let me know.
9b4a7800 196
ee7683eb 197;; * Correspondence
26d654ec 198;;
ee7683eb 199;; Correspondence welcome; please indicate version number. Send bug
9b4a7800 200;; reports and inquiries to <ttn@gnu.org>.
b578f267 201
26a0b399 202;; * Thanks
aaa114d0 203;;
26a0b399
TTN
204;; Thanks go to the following people for valuable ideas, code and
205;; bug reports.
aaa114d0 206;;
2bbf1842
TTN
207;; Dean Andrews, Alf-Ivar Holm, Holger Bauer, Christoph Conrad, Dave Love,
208;; Dirk Herrmann, Gael Marziou, Jan Djarv, Guillaume Leray, Moody Ahmad,
209;; Preston F. Crow, Lars Lindberg, Reto Zimmermann, Keith Sheffield,
c38e0c97 210;; Chew Meng Kuan, Tony Lam, Pete Ware, François Pinard, Stefan Monnier,
2bbf1842 211;; Joseph Eydelnant, Michael Ernst, Peter Heslin
aaa114d0 212;;
9b4a7800
TTN
213;; Special thanks go to Dan Nicolaescu, who reimplemented hideshow using
214;; overlays (rather than selective display), added isearch magic, folded
215;; in custom.el compatibility, generalized comment handling, incorporated
216;; mouse support, and maintained the code in general. Version 4.0 is
217;; largely due to his efforts.
26a0b399
TTN
218
219;; * History
220;;
221;; Hideshow was inspired when I learned about selective display. It was
222;; reimplemented to use overlays for 4.0 (see above). WRT older history,
223;; entries in the masterfile corresponding to versions 1.x and 2.x have
224;; been lost. XEmacs support is reliable as of 4.29. State save and
225;; restore was added in 3.5 (not widely distributed), and reliable as of
226;; 4.30. Otherwise, the code seems stable. Passes checkdoc as of 4.32.
227;; Version 5.x uses new algorithms for block selection and traversal,
228;; unbundles state save and restore, and includes more isearch support.
6da7653c
TTN
229
230;;; Code:
231
26a0b399
TTN
232;;---------------------------------------------------------------------------
233;; user-configurable variables
6da7653c 234
b7c09257
RS
235(defgroup hideshow nil
236 "Minor mode for hiding and showing program and comment blocks."
12e36cdb 237 :prefix "hs-"
b7c09257
RS
238 :group 'languages)
239
aaa114d0 240(defcustom hs-hide-comments-when-hiding-all t
fb7ada5f 241 "Hide the comments too when you do an `hs-hide-all'."
b7c09257
RS
242 :type 'boolean
243 :group 'hideshow)
244
26a0b399 245(defcustom hs-minor-mode-hook nil
fb7ada5f 246 "Hook called when hideshow minor mode is activated or deactivated."
39f2ec46 247 :type 'hook
82dab959
TTN
248 :group 'hideshow
249 :version "21.1")
b7c09257 250
26d654ec 251(defcustom hs-isearch-open 'code
fb7ada5f 252 "What kind of hidden blocks to open when doing `isearch'.
26a0b399 253One of the following symbols:
aaa114d0 254
26d654ec
TTN
255 code -- open only code blocks
256 comment -- open only comment blocks
257 t -- open both code and comment blocks
258 nil -- open neither code nor comment blocks
aaa114d0 259
e7f767c2 260This has effect only if `search-invisible' is set to `open'."
26d654ec
TTN
261 :type '(choice (const :tag "open only code blocks" code)
262 (const :tag "open only comment blocks" comment)
263 (const :tag "open both code and comment blocks" t)
26a0b399 264 (const :tag "don't open any of them" nil))
12e36cdb
RS
265 :group 'hideshow)
266
23d93b6a 267;;;###autoload
aaa114d0 268(defvar hs-special-modes-alist
6bdad9ae 269 (mapcar 'purecopy
1c292fc7
CY
270 '((c-mode "{" "}" "/[*/]" nil nil)
271 (c++-mode "{" "}" "/[*/]" nil nil)
5fe5075b 272 (bibtex-mode ("@\\S(*\\(\\s(\\)" 1))
17b5d0f7 273 (java-mode "{" "}" "/[*/]" nil nil)
6bdad9ae 274 (js-mode "{" "}" "/[*/]" nil)))
fb7ada5f 275 "Alist for initializing the hideshow variables for different modes.
26a0b399 276Each element has the form
aaa114d0 277 (MODE START END COMMENT-START FORWARD-SEXP-FUNC ADJUST-BEG-FUNC).
aaa114d0 278
26a0b399
TTN
279If non-nil, hideshow will use these values as regexps to define blocks
280and comments, respectively for major mode MODE.
281
282START, END and COMMENT-START are regular expressions. A block is
283defined as text surrounded by START and END.
284
285As a special case, START may be a list of the form (COMPLEX-START
286MDATA-SELECTOR), where COMPLEX-START is a regexp w/ multiple parts and
287MDATA-SELECTOR an integer that specifies which sub-match is the proper
90d606ce
TTN
288place to adjust point, before calling `hs-forward-sexp-func'. Point
289is adjusted to the beginning of the specified match. For example,
290see the `hs-special-modes-alist' entry for `bibtex-mode'.
88039caa 291
26a0b399
TTN
292For some major modes, `forward-sexp' does not work properly. In those
293cases, FORWARD-SEXP-FUNC specifies another function to use instead.
294
295See the documentation for `hs-adjust-block-beginning' to see what is the
296use of ADJUST-BEG-FUNC.
297
298If any of the elements is left nil or omitted, hideshow tries to guess
299appropriate values. The regexps should not contain leading or trailing
300whitespace. Case does not matter.")
6da7653c 301
9b4a7800 302(defvar hs-hide-all-non-comment-function nil
fb7ada5f 303 "Function called if non-nil when doing `hs-hide-all' for non-comments.")
9b4a7800 304
a5b101dc 305(defvar hs-allow-nesting nil
fb7ada5f 306 "If non-nil, hiding remembers internal blocks.
d88444f2
JB
307This means that when the outer block is shown again,
308any previously hidden internal blocks remain hidden.")
a5b101dc 309
d877f247 310(defvar hs-hide-hook nil
fb7ada5f 311 "Hook called (with `run-hooks') at the end of commands to hide text.
9b4a7800
TTN
312These commands include the toggling commands (when the result is to hide
313a block), `hs-hide-all', `hs-hide-block' and `hs-hide-level'.")
6da7653c 314
d877f247 315(defvar hs-show-hook nil
fb7ada5f 316 "Hook called (with `run-hooks') at the end of commands to show text.
9b4a7800 317These commands include the toggling commands (when the result is to show
d88444f2 318a block), `hs-show-all' and `hs-show-block'.")
6da7653c 319
dfdc1af2 320(defvar hs-set-up-overlay nil
fb7ada5f 321 "Function called with one arg, OV, a newly initialized overlay.
dfdc1af2
TTN
322Hideshow puts a unique overlay on each range of text to be hidden
323in the buffer. Here is a simple example of how to use this variable:
324
325 (defun display-code-line-counts (ov)
326 (when (eq 'code (overlay-get ov 'hs))
327 (overlay-put ov 'display
328 (format \"... / %d\"
329 (count-lines (overlay-start ov)
330 (overlay-end ov))))))
331
332 (setq hs-set-up-overlay 'display-code-line-counts)
333
334This example shows how to get information from the overlay as well
335as how to set its `display' property. See `hs-make-overlay' and
336info node `(elisp)Overlays'.")
337
26a0b399
TTN
338;;---------------------------------------------------------------------------
339;; internal variables
6da7653c
TTN
340
341(defvar hs-minor-mode nil
c1ff6dac 342 "Non-nil if using hideshow mode as a minor mode of some other mode.
26a0b399 343Use the command `hs-minor-mode' to toggle or set this variable.")
6da7653c 344
1f344760
DN
345(defvar hs-minor-mode-map
346 (let ((map (make-sparse-keymap)))
347 ;; These bindings roughly imitate those used by Outline mode.
348 (define-key map "\C-c@\C-h" 'hs-hide-block)
349 (define-key map "\C-c@\C-s" 'hs-show-block)
350 (define-key map "\C-c@\C-\M-h" 'hs-hide-all)
351 (define-key map "\C-c@\C-\M-s" 'hs-show-all)
352 (define-key map "\C-c@\C-l" 'hs-hide-level)
353 (define-key map "\C-c@\C-c" 'hs-toggle-hiding)
354 (define-key map [(shift mouse-2)] 'hs-mouse-toggle-hiding)
1f344760 355 map)
26a0b399 356 "Keymap for hideshow minor mode.")
6da7653c 357
d79dd1b0
DN
358(easy-menu-define hs-minor-mode-menu hs-minor-mode-map
359 "Menu used when hideshow minor mode is active."
360 '("Hide/Show"
361 ["Hide Block" hs-hide-block
362 :help "Hide the code or comment block at point"]
363 ["Show Block" hs-show-block
364 :help "Show the code or comment block at point"]
365 ["Hide All" hs-hide-all
366 :help "Hide all the blocks in the buffer"]
367 ["Show All" hs-show-all
76cdeb7d 368 :help "Show all the blocks in the buffer"]
d79dd1b0
DN
369 ["Hide Level" hs-hide-level
370 :help "Hide all block at levels below the current block"]
371 ["Toggle Hiding" hs-toggle-hiding
76cdeb7d
DN
372 :help "Toggle the hiding state of the current block"]
373 "----"
fb652bb5 374 ["Hide comments when hiding all"
76cdeb7d
DN
375 (setq hs-hide-comments-when-hiding-all
376 (not hs-hide-comments-when-hiding-all))
377 :help "If t also hide comment blocks when doing `hs-hide-all'"
378 :style toggle :selected hs-hide-comments-when-hiding-all]
379 ("Reveal on isearch"
380 ["Code blocks" (setq hs-isearch-open 'code)
381 :help "Show hidden code blocks when isearch matches inside them"
382 :active t :style radio :selected (eq hs-isearch-open 'code)]
383 ["Comment blocks" (setq hs-isearch-open 'comment)
384 :help "Show hidden comment blocks when isearch matches inside them"
385 :active t :style radio :selected (eq hs-isearch-open 'comment)]
386 ["Code and Comment blocks" (setq hs-isearch-open t)
387 :help "Show both hidden code and comment blocks when isearch matches inside them"
5c842d76 388 :active t :style radio :selected (eq hs-isearch-open t)]
76cdeb7d
DN
389 ["None" (setq hs-isearch-open nil)
390 :help "Do not hidden code or comment blocks when isearch matches inside them"
391 :active t :style radio :selected (eq hs-isearch-open nil)])))
d79dd1b0 392
6da7653c 393(defvar hs-c-start-regexp nil
aaa114d0
TTN
394 "Regexp for beginning of comments.
395Differs from mode-specific comment regexps in that
9479d258 396surrounding whitespace is stripped.")
1f344760 397(make-variable-buffer-local 'hs-c-start-regexp)
6da7653c 398
6da7653c 399(defvar hs-block-start-regexp nil
9479d258 400 "Regexp for beginning of block.")
1f344760 401(make-variable-buffer-local 'hs-block-start-regexp)
6da7653c 402
26a0b399
TTN
403(defvar hs-block-start-mdata-select nil
404 "Element in `hs-block-start-regexp' match data to consider as block start.
405The internal function `hs-forward-sexp' moves point to the beginning of this
406element (using `match-beginning') before calling `hs-forward-sexp-func'.")
1f344760 407(make-variable-buffer-local 'hs-block-start-mdata-select)
26a0b399 408
6da7653c 409(defvar hs-block-end-regexp nil
9479d258 410 "Regexp for end of block.")
d30aca1b
DN
411(make-variable-buffer-local 'hs-block-end-regexp)
412
6da7653c
TTN
413
414(defvar hs-forward-sexp-func 'forward-sexp
aaa114d0 415 "Function used to do a `forward-sexp'.
9479d258
RS
416Should change for Algol-ish modes. For single-character block
417delimiters -- ie, the syntax table regexp for the character is
aaa114d0
TTN
418either `(' or `)' -- `hs-forward-sexp-func' would just be
419`forward-sexp'. For other modes such as simula, a more specialized
420function is necessary.")
1f344760 421(make-variable-buffer-local 'hs-forward-sexp-func)
6da7653c 422
88039caa
RS
423(defvar hs-adjust-block-beginning nil
424 "Function used to tweak the block beginning.
26a0b399
TTN
425The block is hidden from the position returned by this function,
426as opposed to hiding it from the position returned when searching
427for `hs-block-start-regexp'.
428
429For example, in c-like modes, if we wish to also hide the curly braces
c98cd8bb 430\(if you think they occupy too much space on the screen), this function
26a0b399
TTN
431should return the starting point (at the end of line) of the hidden
432region.
88039caa 433
e7bdfdcf 434It is called with a single argument ARG which is the position in
88039caa
RS
435buffer after the block beginning.
436
437It should return the position from where we should start hiding.
438
aaa114d0 439It should not move the point.
88039caa 440
d06970e5 441See `hs-c-like-adjust-block-beginning' for an example of using this.")
1f344760 442(make-variable-buffer-local 'hs-adjust-block-beginning)
c1ff6dac 443
26a0b399
TTN
444(defvar hs-headline nil
445 "Text of the line where a hidden block begins, set during isearch.
446You can display this in the mode line by adding the symbol `hs-headline'
447to the variable `mode-line-format'. For example,
448
449 (unless (memq 'hs-headline mode-line-format)
450 (setq mode-line-format
451 (append '(\"-\" hs-headline) mode-line-format)))
452
453Note that `mode-line-format' is buffer-local.")
454
26a0b399
TTN
455;;---------------------------------------------------------------------------
456;; support functions
457
458(defun hs-discard-overlays (from to)
a5b101dc
TTN
459 "Delete hideshow overlays in region defined by FROM and TO.
460Skip \"internal\" overlays if `hs-allow-nesting' is non-nil."
26a0b399
TTN
461 (when (< to from)
462 (setq from (prog1 to (setq to from))))
a5b101dc
TTN
463 (if hs-allow-nesting
464 (let (ov)
465 (while (> to (setq from (next-overlay-change from)))
466 (when (setq ov (hs-overlay-at from))
467 (setq from (overlay-end ov))
468 (delete-overlay ov))))
469 (dolist (ov (overlays-in from to))
470 (when (overlay-get ov 'hs)
471 (delete-overlay ov)))))
26a0b399 472
dfdc1af2
TTN
473(defun hs-make-overlay (b e kind &optional b-offset e-offset)
474 "Return a new overlay in region defined by B and E with type KIND.
475KIND is either `code' or `comment'. Optional fourth arg B-OFFSET
476when added to B specifies the actual buffer position where the block
477begins. Likewise for optional fifth arg E-OFFSET. If unspecified
478they are taken to be 0 (zero). The following properties are set
479in the overlay: 'invisible 'hs 'hs-b-offset 'hs-e-offset. Also,
480depending on variable `hs-isearch-open', the following properties may
481be present: 'isearch-open-invisible 'isearch-open-invisible-temporary.
482If variable `hs-set-up-overlay' is non-nil it should specify a function
483to call with the newly initialized overlay."
484 (unless b-offset (setq b-offset 0))
485 (unless e-offset (setq e-offset 0))
486 (let ((ov (make-overlay b e))
487 (io (if (eq 'block hs-isearch-open)
488 ;; backward compatibility -- `block'<=>`code'
489 'code
490 hs-isearch-open)))
491 (overlay-put ov 'invisible 'hs)
492 (overlay-put ov 'hs kind)
493 (overlay-put ov 'hs-b-offset b-offset)
494 (overlay-put ov 'hs-e-offset e-offset)
495 (when (or (eq io t) (eq io kind))
496 (overlay-put ov 'isearch-open-invisible 'hs-isearch-show)
497 (overlay-put ov 'isearch-open-invisible-temporary
498 'hs-isearch-show-temporary))
499 (when hs-set-up-overlay (funcall hs-set-up-overlay ov))
500 ov))
501
26a0b399 502(defun hs-isearch-show (ov)
1a8e83dc
TTN
503 "Delete overlay OV, and set `hs-headline' to nil.
504
505This function is meant to be used as the `isearch-open-invisible'
506property of an overlay."
26a0b399 507 (setq hs-headline nil)
1a8e83dc 508 (delete-overlay ov))
26a0b399
TTN
509
510(defun hs-isearch-show-temporary (ov hide-p)
1a8e83dc
TTN
511 "Hide or show overlay OV, and set `hs-headline', all depending on HIDE-P.
512If HIDE-P is non-nil, `hs-headline' is set to nil and overlay OV is hidden.
513Otherwise, `hs-headline' is set to the line of text at the head of OV, and
514OV is shown.
515
516This function is meant to be used as the `isearch-open-invisible-temporary'
517property of an overlay."
26a0b399 518 (setq hs-headline
53c40303
TTN
519 (if hide-p
520 nil
521 (or hs-headline
522 (let ((start (overlay-start ov)))
523 (buffer-substring
524 (save-excursion (goto-char start)
525 (beginning-of-line)
526 (skip-chars-forward " \t")
527 (point))
528 start)))))
26a0b399 529 (force-mode-line-update)
dfdc1af2
TTN
530 ;; handle `display' property specially
531 (let (value)
532 (if hide-p
533 (when (setq value (overlay-get ov 'hs-isearch-display))
534 (overlay-put ov 'display value)
535 (overlay-put ov 'hs-isearch-display nil))
536 (when (setq value (overlay-get ov 'display))
537 (overlay-put ov 'hs-isearch-display value)
538 (overlay-put ov 'display nil))))
26a0b399 539 (overlay-put ov 'invisible (and hide-p 'hs)))
6da7653c 540
5012f24c
DK
541(defun hs-looking-at-block-start-p ()
542 "Return non-nil if the point is at the block start."
543 (and (looking-at hs-block-start-regexp)
bfeef8b6 544 (save-match-data (not (nth 8 (syntax-ppss))))))
5012f24c 545
26a0b399
TTN
546(defun hs-forward-sexp (match-data arg)
547 "Adjust point based on MATCH-DATA and call `hs-forward-sexp-func' w/ ARG.
548Original match data is restored upon return."
549 (save-match-data
550 (set-match-data match-data)
551 (goto-char (match-beginning hs-block-start-mdata-select))
552 (funcall hs-forward-sexp-func arg)))
553
554(defun hs-hide-comment-region (beg end &optional repos-end)
555 "Hide a region from BEG to END, marking it as a comment.
556Optional arg REPOS-END means reposition at end."
77a2bb53
TTN
557 (let ((beg-eol (progn (goto-char beg) (line-end-position)))
558 (end-eol (progn (goto-char end) (line-end-position))))
dfdc1af2
TTN
559 (hs-discard-overlays beg-eol end-eol)
560 (hs-make-overlay beg-eol end-eol 'comment beg end))
26a0b399 561 (goto-char (if repos-end end beg)))
9479d258 562
88039caa 563(defun hs-hide-block-at-point (&optional end comment-reg)
e7f767c2 564 "Hide block if on block beginning.
aaa114d0 565Optional arg END means reposition at end.
26a0b399 566Optional arg COMMENT-REG is a list of the form (BEGIN END) and
aaa114d0 567specifies the limits of the comment, or nil if the block is not
26a0b399
TTN
568a comment.
569
570The block beginning is adjusted by `hs-adjust-block-beginning'
571and then further adjusted to be at the end of the line."
88039caa 572 (if comment-reg
26a0b399 573 (hs-hide-comment-region (car comment-reg) (cadr comment-reg) end)
5012f24c 574 (when (hs-looking-at-block-start-p)
e02f48d7
JB
575 (let ((mdata (match-data t))
576 (header-end (match-end 0))
577 p q ov)
1c292fc7
CY
578 ;; `p' is the point at the end of the block beginning, which
579 ;; may need to be adjusted
580 (save-excursion
581 (if hs-adjust-block-beginning
582 (goto-char (funcall hs-adjust-block-beginning
583 header-end))
584 (goto-char header-end))
585 (setq p (line-end-position)))
586 ;; `q' is the point at the end of the block
587 (hs-forward-sexp mdata 1)
588 (setq q (if (looking-back hs-block-end-regexp)
589 (match-beginning 0)
590 (point)))
591 (when (and (< p q) (> (count-lines p q) 1))
a5b101dc
TTN
592 (cond ((and hs-allow-nesting (setq ov (hs-overlay-at p)))
593 (delete-overlay ov))
594 ((not hs-allow-nesting)
595 (hs-discard-overlays p q)))
d44d05e8
CY
596 (hs-make-overlay p q 'code (- header-end p)))
597 (goto-char (if end q (min p header-end)))))))
6da7653c 598
6da7653c 599(defun hs-inside-comment-p ()
aaa114d0 600 "Return non-nil if point is inside a comment, otherwise nil.
26a0b399 601Actually, return a list containing the buffer position of the start
aaa114d0
TTN
602and the end of the comment. A comment block can be hidden only if on
603its starting line there is only whitespace preceding the actual comment
604beginning. If we are inside of a comment but this condition is not met,
88039caa
RS
605we return a list having a nil as its car and the end of comment position
606as cdr."
aaa114d0 607 (save-excursion
88039caa 608 ;; the idea is to look backwards for a comment start regexp, do a
5a5fa834 609 ;; forward comment, and see if we are inside, then extend
88039caa
RS
610 ;; forward and backward as long as we have comments
611 (let ((q (point)))
91d82a70 612 (skip-chars-forward "[:blank:]")
88039caa 613 (when (or (looking-at hs-c-start-regexp)
26a0b399 614 (re-search-backward hs-c-start-regexp (point-min) t))
aa7d6700
TTN
615 ;; first get to the beginning of this comment...
616 (while (and (not (bobp))
617 (= (point) (progn (forward-comment -1) (point))))
618 (forward-char -1))
619 ;; ...then extend backwards
26a0b399
TTN
620 (forward-comment (- (buffer-size)))
621 (skip-chars-forward " \t\n\f")
622 (let ((p (point))
aa7d6700 623 (hidable t))
26a0b399
TTN
624 (beginning-of-line)
625 (unless (looking-at (concat "[ \t]*" hs-c-start-regexp))
626 ;; we are in this situation: (example)
627 ;; (defun bar ()
628 ;; (foo)
629 ;; ) ; comment
630 ;; ^
631 ;; the point was here before doing (beginning-of-line)
632 ;; here we should advance till the next comment which
633 ;; eventually has only white spaces preceding it on the same
634 ;; line
635 (goto-char p)
636 (forward-comment 1)
637 (skip-chars-forward " \t\n\f")
638 (setq p (point))
639 (while (and (< (point) q)
640 (> (point) p)
641 (not (looking-at hs-c-start-regexp)))
2bbf1842
TTN
642 ;; avoid an infinite cycle
643 (setq p (point))
26a0b399
TTN
644 (forward-comment 1)
645 (skip-chars-forward " \t\n\f"))
c327c405
TTN
646 (when (or (not (looking-at hs-c-start-regexp))
647 (> (point) q))
648 ;; we cannot hide this comment block
aa7d6700 649 (setq hidable nil)))
26a0b399
TTN
650 ;; goto the end of the comment
651 (forward-comment (buffer-size))
652 (skip-chars-backward " \t\n\f")
653 (end-of-line)
c327c405 654 (when (>= (point) q)
aa7d6700 655 (list (and hidable p) (point))))))))
6da7653c
TTN
656
657(defun hs-grok-mode-type ()
aaa114d0
TTN
658 "Set up hideshow variables for new buffers.
659If `hs-special-modes-alist' has information associated with the
660current buffer's major mode, use that.
26a0b399 661Otherwise, guess start, end and `comment-start' regexps; `forward-sexp'
aaa114d0 662function; and adjust-block-beginning function."
b5190045 663 (if (and (boundp 'comment-start)
26a0b399
TTN
664 (boundp 'comment-end)
665 comment-start comment-end)
666 (let* ((lookup (assoc major-mode hs-special-modes-alist))
667 (start-elem (or (nth 1 lookup) "\\s(")))
668 (if (listp start-elem)
669 ;; handle (START-REGEXP MDATA-SELECT)
670 (setq hs-block-start-regexp (car start-elem)
671 hs-block-start-mdata-select (cadr start-elem))
672 ;; backwards compatibility: handle simple START-REGEXP
673 (setq hs-block-start-regexp start-elem
674 hs-block-start-mdata-select 0))
675 (setq hs-block-end-regexp (or (nth 2 lookup) "\\s)")
676 hs-c-start-regexp (or (nth 3 lookup)
677 (let ((c-start-regexp
678 (regexp-quote comment-start)))
679 (if (string-match " +$" c-start-regexp)
680 (substring c-start-regexp
681 0 (1- (match-end 0)))
682 c-start-regexp)))
683 hs-forward-sexp-func (or (nth 4 lookup) 'forward-sexp)
684 hs-adjust-block-beginning (nth 5 lookup)))
c327c405 685 (setq hs-minor-mode nil)
48d33090
SM
686 (error "%s Mode doesn't support Hideshow Minor Mode"
687 (format-mode-line mode-name))))
6da7653c
TTN
688
689(defun hs-find-block-beginning ()
aaa114d0 690 "Reposition point at block-start.
1a8e83dc 691Return point, or nil if original point was not in a block."
26a0b399
TTN
692 (let ((done nil)
693 (here (point)))
694 ;; look if current line is block start
5012f24c 695 (if (hs-looking-at-block-start-p)
26a0b399
TTN
696 (point)
697 ;; look backward for the start of a block that contains the cursor
698 (while (and (re-search-backward hs-block-start-regexp nil t)
bfeef8b6
DK
699 ;; go again if in a comment or a string
700 (or (save-match-data (nth 8 (syntax-ppss)))
5012f24c
DK
701 (not (setq done
702 (< here (save-excursion
703 (hs-forward-sexp (match-data t) 1)
704 (point))))))))
26a0b399
TTN
705 (if done
706 (point)
707 (goto-char here)
708 nil))))
6da7653c 709
aaa114d0 710(defun hs-hide-level-recursive (arg minp maxp)
26a0b399 711 "Recursively hide blocks ARG levels below point in region (MINP MAXP)."
aaa114d0
TTN
712 (when (hs-find-block-beginning)
713 (setq minp (1+ (point)))
26a0b399 714 (funcall hs-forward-sexp-func 1)
aaa114d0 715 (setq maxp (1- (point))))
a5b101dc
TTN
716 (unless hs-allow-nesting
717 (hs-discard-overlays minp maxp))
aaa114d0
TTN
718 (goto-char minp)
719 (while (progn
26a0b399
TTN
720 (forward-comment (buffer-size))
721 (and (< (point) maxp)
722 (re-search-forward hs-block-start-regexp maxp t)))
a87af185 723 (when (save-match-data
bfeef8b6 724 (not (nth 8 (syntax-ppss)))) ; not inside comments or strings
a87af185
DB
725 (if (> arg 1)
726 (hs-hide-level-recursive (1- arg) minp maxp)
727 (goto-char (match-beginning hs-block-start-mdata-select))
728 (hs-hide-block-at-point t))))
aaa114d0
TTN
729 (goto-char maxp))
730
6da7653c 731(defmacro hs-life-goes-on (&rest body)
e7f767c2 732 "Evaluate BODY forms if variable `hs-minor-mode' is non-nil.
26a0b399
TTN
733In the dynamic context of this macro, `inhibit-point-motion-hooks'
734and `case-fold-search' are both t."
735 `(when hs-minor-mode
736 (let ((inhibit-point-motion-hooks t)
737 (case-fold-search t))
12ab6a7d
DN
738 ,@body)))
739
5e336d3e 740(put 'hs-life-goes-on 'edebug-form-spec '(&rest form))
6da7653c 741
a5b101dc
TTN
742(defun hs-overlay-at (position)
743 "Return hideshow overlay at POSITION, or nil if none to be found."
744 (let ((overlays (overlays-at position))
745 ov found)
746 (while (and (not found) (setq ov (car overlays)))
747 (setq found (and (overlay-get ov 'hs) ov)
748 overlays (cdr overlays)))
749 found))
750
9479d258 751(defun hs-already-hidden-p ()
88039caa 752 "Return non-nil if point is in an already-hidden block, otherwise nil."
9479d258 753 (save-excursion
88039caa
RS
754 (let ((c-reg (hs-inside-comment-p)))
755 (if (and c-reg (nth 0 c-reg))
26a0b399
TTN
756 ;; point is inside a comment, and that comment is hidable
757 (goto-char (nth 0 c-reg))
dce39ca8 758 (end-of-line)
c327c405
TTN
759 (when (and (not c-reg)
760 (hs-find-block-beginning)
5012f24c 761 (hs-looking-at-block-start-p))
c327c405
TTN
762 ;; point is inside a block
763 (goto-char (match-end 0)))))
9479d258 764 (end-of-line)
a5b101dc 765 (hs-overlay-at (point))))
9479d258 766
1c292fc7 767;; This function is not used anymore (Bug#700).
26a0b399
TTN
768(defun hs-c-like-adjust-block-beginning (initial)
769 "Adjust INITIAL, the buffer position after `hs-block-start-regexp'.
770Actually, point is never moved; a new position is returned that is
771the end of the C-function header. This adjustment function is meant
772to be assigned to `hs-adjust-block-beginning' for C-like modes."
88039caa 773 (save-excursion
26a0b399 774 (goto-char (1- initial))
88039caa
RS
775 (forward-comment (- (buffer-size)))
776 (point)))
777
26a0b399
TTN
778;;---------------------------------------------------------------------------
779;; commands
6da7653c 780
6da7653c 781(defun hs-hide-all ()
26a0b399
TTN
782 "Hide all top level blocks, displaying only first and last lines.
783Move point to the beginning of the line, and run the normal hook
aaa114d0 784`hs-hide-hook'. See documentation for `run-hooks'.
26a0b399 785If `hs-hide-comments-when-hiding-all' is non-nil, also hide the comments."
6da7653c
TTN
786 (interactive)
787 (hs-life-goes-on
6da7653c 788 (save-excursion
a5b101dc
TTN
789 (unless hs-allow-nesting
790 (hs-discard-overlays (point-min) (point-max)))
6da7653c 791 (goto-char (point-min))
31a0385d
TTN
792 (let ((spew (make-progress-reporter "Hiding all blocks..."
793 (point-min) (point-max)))
9b4a7800
TTN
794 (re (concat "\\("
795 hs-block-start-regexp
796 "\\)"
797 (if hs-hide-comments-when-hiding-all
798 (concat "\\|\\("
799 hs-c-start-regexp
800 "\\)")
801 ""))))
802 (while (progn
803 (unless hs-hide-comments-when-hiding-all
804 (forward-comment (point-max)))
805 (re-search-forward re (point-max) t))
806 (if (match-beginning 1)
43956923 807 ;; We have found a block beginning.
26a0b399 808 (progn
9b4a7800 809 (goto-char (match-beginning 1))
43956923
SG
810 (unless (if hs-hide-all-non-comment-function
811 (funcall hs-hide-all-non-comment-function)
812 (hs-hide-block-at-point t))
813 ;; Go to end of matched data to prevent from getting stuck
814 ;; with an endless loop.
815 (goto-char (match-end 0))))
9b4a7800 816 ;; found a comment, probably
2bbf1842 817 (let ((c-reg (hs-inside-comment-p)))
9b4a7800
TTN
818 (when (and c-reg (car c-reg))
819 (if (> (count-lines (car c-reg) (nth 1 c-reg)) 1)
820 (hs-hide-block-at-point t c-reg)
821 (goto-char (nth 1 c-reg))))))
31a0385d
TTN
822 (progress-reporter-update spew (point)))
823 (progress-reporter-done spew)))
6da7653c 824 (beginning-of-line)
d877f247 825 (run-hooks 'hs-hide-hook)))
6da7653c
TTN
826
827(defun hs-show-all ()
26a0b399 828 "Show everything then run `hs-show-hook'. See `run-hooks'."
6da7653c
TTN
829 (interactive)
830 (hs-life-goes-on
9479d258 831 (message "Showing all blocks ...")
a5b101dc
TTN
832 (let ((hs-allow-nesting nil))
833 (hs-discard-overlays (point-min) (point-max)))
9479d258 834 (message "Showing all blocks ... done")
d877f247 835 (run-hooks 'hs-show-hook)))
6da7653c 836
6da7653c 837(defun hs-hide-block (&optional end)
26a0b399 838 "Select a block and hide it. With prefix arg, reposition at END.
aaa114d0 839Upon completion, point is repositioned and the normal hook
9479d258 840`hs-hide-hook' is run. See documentation for `run-hooks'."
6da7653c
TTN
841 (interactive "P")
842 (hs-life-goes-on
843 (let ((c-reg (hs-inside-comment-p)))
88039caa 844 (cond
aaa114d0 845 ((and c-reg (or (null (nth 0 c-reg))
26a0b399
TTN
846 (<= (count-lines (car c-reg) (nth 1 c-reg)) 1)))
847 (message "(not enough comment lines to hide)"))
848 ((or c-reg
5012f24c 849 (hs-looking-at-block-start-p)
26a0b399 850 (hs-find-block-beginning))
88039caa 851 (hs-hide-block-at-point end c-reg)
88039caa 852 (run-hooks 'hs-hide-hook))))))
6da7653c
TTN
853
854(defun hs-show-block (&optional end)
aaa114d0 855 "Select a block and show it.
26a0b399 856With prefix arg, reposition at END. Upon completion, point is
aaa114d0 857repositioned and the normal hook `hs-show-hook' is run.
26a0b399 858See documentation for functions `hs-hide-block' and `run-hooks'."
6da7653c
TTN
859 (interactive "P")
860 (hs-life-goes-on
26a0b399
TTN
861 (or
862 ;; first see if we have something at the end of the line
77a2bb53 863 (let ((ov (hs-overlay-at (line-end-position)))
a5b101dc
TTN
864 (here (point)))
865 (when ov
866 (goto-char
867 (cond (end (overlay-end ov))
868 ((eq 'comment (overlay-get ov 'hs)) here)
869 (t (+ (overlay-start ov) (overlay-get ov 'hs-b-offset)))))
870 (delete-overlay ov)
871 t))
26a0b399
TTN
872 ;; not immediately obvious, look for a suitable block
873 (let ((c-reg (hs-inside-comment-p))
874 p q)
875 (cond (c-reg
876 (when (car c-reg)
877 (setq p (car c-reg)
878 q (cadr c-reg))))
879 ((and (hs-find-block-beginning)
2bbf1842 880 ;; ugh, fresh match-data
5012f24c 881 (hs-looking-at-block-start-p))
26a0b399 882 (setq p (point)
39696a77 883 q (progn (hs-forward-sexp (match-data t) 1) (point)))))
26a0b399 884 (when (and p q)
dfdc1af2 885 (hs-discard-overlays p q)
fb652bb5
JB
886 (goto-char (if end q (1+ p))))))
887 (run-hooks 'hs-show-hook)))
6da7653c 888
aaa114d0 889(defun hs-hide-level (arg)
26a0b399
TTN
890 "Hide all blocks ARG levels below this block.
891The hook `hs-hide-hook' is run; see `run-hooks'."
aaa114d0
TTN
892 (interactive "p")
893 (hs-life-goes-on
894 (save-excursion
895 (message "Hiding blocks ...")
896 (hs-hide-level-recursive arg (point-min) (point-max))
897 (message "Hiding blocks ... done"))
aaa114d0
TTN
898 (run-hooks 'hs-hide-hook)))
899
a23c5037
TTN
900(defun hs-toggle-hiding ()
901 "Toggle hiding/showing of a block.
902See `hs-hide-block' and `hs-show-block'."
903 (interactive)
904 (hs-life-goes-on
905 (if (hs-already-hidden-p)
906 (hs-show-block)
907 (hs-hide-block))))
908
9479d258 909(defun hs-mouse-toggle-hiding (e)
aaa114d0 910 "Toggle hiding/showing of a block.
26a0b399
TTN
911This command should be bound to a mouse key.
912Argument E is a mouse event used by `mouse-set-point'.
913See `hs-hide-block' and `hs-show-block'."
9479d258 914 (interactive "@e")
26a0b399
TTN
915 (hs-life-goes-on
916 (mouse-set-point e)
a23c5037 917 (hs-toggle-hiding)))
26a0b399
TTN
918
919(defun hs-hide-initial-comment-block ()
920 "Hide the first block of comments in a file.
921This can be useful if you have huge RCS logs in those comments."
922 (interactive)
923 (hs-life-goes-on
924 (let ((c-reg (save-excursion
925 (goto-char (point-min))
926 (skip-chars-forward " \t\n\f")
927 (hs-inside-comment-p))))
928 (when c-reg
929 (let ((beg (car c-reg)) (end (cadr c-reg)))
930 ;; see if we have enough comment lines to hide
931 (when (> (count-lines beg end) 1)
932 (hs-hide-comment-region beg end)))))))
9479d258 933
6da7653c 934;;;###autoload
1f344760 935(define-minor-mode hs-minor-mode
fb652bb5 936 "Minor mode to selectively hide/show code and comment blocks.
e1ac4066
GM
937With a prefix argument ARG, enable the mode if ARG is positive,
938and disable it otherwise. If called from Lisp, enable the mode
939if ARG is omitted or nil.
940
c1ff6dac 941When hideshow minor mode is on, the menu bar is augmented with hideshow
aaa114d0 942commands and the hideshow commands are enabled.
88039caa 943The value '(hs . t) is added to `buffer-invisibility-spec'.
d877f247 944
aaa114d0 945The main commands are: `hs-hide-all', `hs-show-all', `hs-hide-block',
9b4a7800 946`hs-show-block', `hs-hide-level' and `hs-toggle-hiding'. There is also
26a0b399 947`hs-hide-initial-comment-block' and `hs-mouse-toggle-hiding'.
88039caa 948
c1ff6dac 949Turning hideshow minor mode off reverts the menu bar and the
88039caa
RS
950variables to default values and disables the hideshow commands.
951
a23c5037
TTN
952Lastly, the normal hook `hs-minor-mode-hook' is run using `run-hooks'.
953
88039caa
RS
954Key bindings:
955\\{hs-minor-mode-map}"
fb652bb5 956 :group 'hideshow
1f344760
DN
957 :lighter " hs"
958 :keymap hs-minor-mode-map
959 (setq hs-headline nil)
6da7653c
TTN
960 (if hs-minor-mode
961 (progn
a23c5037 962 (hs-grok-mode-type)
b9634f95 963 ;; Turn off this mode if we change major modes.
dce39ca8
TTN
964 (add-hook 'change-major-mode-hook
965 'turn-off-hideshow
966 nil t)
26a0b399 967 (easy-menu-add hs-minor-mode-menu)
3f3960a5 968 (set (make-local-variable 'line-move-ignore-invisible) t)
a23c5037 969 (add-to-invisibility-spec '(hs . t)))
b9634f95
GM
970 (remove-from-invisibility-spec '(hs . t))
971 ;; hs-show-all does nothing unless h-m-m is non-nil.
972 (let ((hs-minor-mode t))
1f344760 973 (hs-show-all))))
6da7653c 974
996ae468
CY
975;;;###autoload
976(defun turn-off-hideshow ()
1fee5894 977 "Unconditionally turn off `hs-minor-mode'."
996ae468
CY
978 (hs-minor-mode -1))
979
26a0b399
TTN
980;;---------------------------------------------------------------------------
981;; that's it
6da7653c
TTN
982
983(provide 'hideshow)
984
985;;; hideshow.el ends here