Commit | Line | Data |
---|---|---|
6da7653c TTN |
1 | ;;; hideshow.el --- minor mode cmds to selectively display blocks of code |
2 | ||
23d93b6a | 3 | ;; Copyright (C) 1994, 1995, 1996, 1997 Free Software Foundation |
b578f267 EN |
4 | |
5 | ;; Author: Thien-Thi Nguyen <ttn@netcom.com> | |
9479d258 RS |
6 | ;; Maintainer: Dan Nicolaescu <done@ece.arizona.edu> |
7 | ;; Version: 4.0 | |
b7c09257 | 8 | ;; Keywords: C C++ java lisp tools editing comments blocks hiding outlines |
b578f267 EN |
9 | ;; Time-of-Day-Author-Most-Likely-to-be-Recalcitrant: early morning |
10 | ||
11 | ;; This file is part of GNU Emacs. | |
12 | ||
13 | ;; GNU Emacs is free software; you can redistribute it and/or modify | |
14 | ;; it under the terms of the GNU General Public License as published by | |
15 | ;; the Free Software Foundation; either version 2, or (at your option) | |
16 | ;; any later version. | |
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 | |
24 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | |
25 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
26 | ;; Boston, MA 02111-1307, USA. | |
27 | ||
28 | ;; LCD Archive Entry: | |
29 | ;; hideshow|Thien-Thi Nguyen|ttn@netcom.com| | |
30 | ;; minor mode commands to selectively display blocks of code| | |
31 | ;; 18-Oct-1994|3.4|~/modes/hideshow.el.Z| | |
6da7653c TTN |
32 | |
33 | ;;; Commentary: | |
34 | ||
b578f267 EN |
35 | ;; This file provides `hs-minor-mode'. When active, six commands: |
36 | ;; hs-{hide,show}-{all,block}, hs-show-region and hs-minor-mode | |
37 | ;; are available. They implement block hiding and showing. Blocks are | |
38 | ;; defined in mode-specific way. In c-mode or c++-mode, they are simply | |
39 | ;; curly braces, while in lisp-ish modes they are parens. Multi-line | |
40 | ;; comments (c-mode) can also be hidden. The command M-x hs-minor-mode | |
41 | ;; toggles the minor mode or sets it (similar to outline minor mode). | |
42 | ;; See documentation for each command for more info. | |
43 | ;; | |
44 | ;; The variable `hs-unbalance-handler-method' controls hideshow's behavior | |
45 | ;; in the case of "unbalanced parentheses". See doc for more info. | |
46 | ||
47 | ;; Suggested usage: | |
48 | ||
49 | ;; (load-library "hideshow") | |
9479d258 | 50 | ;; (add-hook 'X-mode-hook 'hs-minor-mode) ; other modes similarly |
b578f267 EN |
51 | ;; |
52 | ;; where X = {emacs-lisp,c,c++,perl,...}. See the doc for the variable | |
53 | ;; `hs-special-modes-alist' if you'd like to use hideshow w/ other modes. | |
54 | ||
55 | ;; Etc: | |
56 | ||
57 | ;; Bug reports and fixes welcome (comments, too). Thanks go to | |
58 | ;; Dean Andrews <adahome@ix.netcom.com> | |
59 | ;; Preston F. Crow <preston.f.crow@dartmouth.edu> | |
60 | ;; Gael Marziou <gael@gnlab030.grenoble.hp.com> | |
61 | ;; Keith Sheffield <sheff@edcsgw2.cr.usgs.gov> | |
62 | ;; Jan Djarv <jan.djarv@sa.erisoft.se> | |
63 | ;; Lars Lindberg <qhslali@aom.ericsson.se> | |
64 | ;; Alf-Ivar Holm <alfh@ifi.uio.no> | |
65 | ;; for valuable feedback, code and bug reports. | |
6da7653c TTN |
66 | |
67 | ;;; Code: | |
68 | ||
69 | ||
6da7653c TTN |
70 | ;;;---------------------------------------------------------------------------- |
71 | ;;; user-configurable variables | |
72 | ||
b7c09257 RS |
73 | (defgroup hideshow nil |
74 | "Minor mode for hiding and showing program and comment blocks." | |
12e36cdb | 75 | :prefix "hs-" |
b7c09257 RS |
76 | :group 'languages) |
77 | ||
23d93b6a | 78 | ;;;###autoload |
b7c09257 RS |
79 | (defcustom hs-hide-comments-when-hiding-all t |
80 | "Hide the comments too when you do an `hs-hide-all'." | |
81 | :type 'boolean | |
82 | :group 'hideshow) | |
83 | ||
23d93b6a | 84 | ;;;###autoload |
b7c09257 RS |
85 | (defcustom hs-show-hidden-short-form t |
86 | "Leave only the first line visible in a hidden block. | |
87 | If t only the first line is visible when a block is in the hidden state, | |
88 | else both the first line and the last line are showed. Also if t and | |
89 | `hs-adjust-block-beginning' is set, it is used also. | |
90 | ||
91 | An example of how this works: (in c-mode) | |
92 | original: | |
93 | ||
94 | /* My function main | |
95 | some more stuff about main | |
96 | */ | |
97 | int | |
98 | main(void) | |
99 | { | |
100 | int x=0; | |
101 | return 0; | |
102 | } | |
103 | ||
104 | ||
105 | hidden and hs-show-hidden-short-form is nil | |
106 | /* My function main... | |
107 | */ | |
108 | int | |
109 | main(void) | |
110 | {... | |
111 | } | |
112 | ||
113 | hidden and hs-show-hidden-short-form is t | |
114 | /* My function main... | |
115 | int | |
d06970e5 | 116 | main(void)... |
b7c09257 | 117 | |
d06970e5 RS |
118 | For latest you have to be on the line containing the ellipsis when |
119 | you do `hs-show-block'." | |
b7c09257 RS |
120 | :type 'boolean |
121 | :group 'hideshow) | |
122 | ||
123 | (defcustom hs-minor-mode-hook 'hs-hide-initial-comment-block | |
124 | "Hook called when `hs-minor-mode' is installed. | |
125 | A good value for this would be `hs-hide-initial-comment-block' to | |
126 | hide all the comments at the beginning of the file." | |
39f2ec46 | 127 | :type 'hook |
b7c09257 RS |
128 | :group 'hideshow) |
129 | ||
12e36cdb RS |
130 | (defcustom hs-isearch-open 'block |
131 | "What kind of hidden blocks to open when doing `isearch'. | |
132 | It can have the following values: | |
133 | `block' open only blocks | |
134 | `comment' open only comments | |
135 | t open all of them | |
136 | nil don't open any. | |
137 | This only has effect iff `search-invisible' is set to `open'." | |
138 | :type '(choice (const :tag "open only blocks" block) | |
139 | (const :tag "open only comments" comment) | |
140 | (const :tag "open both blocks and comments" t) | |
141 | (const :tag "don't open any of them" nil)) | |
142 | :group 'hideshow) | |
143 | ||
6da7653c | 144 | (defvar hs-unbalance-handler-method 'top-level |
c1ff6dac | 145 | "*Symbol representing how \"unbalanced parentheses\" should be handled. |
d877f247 | 146 | This error is usually signaled by `hs-show-block'. One of four values: |
c1ff6dac | 147 | `top-level', `next-line', `signal' or `ignore'. Default is `top-level'. |
6da7653c | 148 | |
c1ff6dac | 149 | - `top-level' -- Show top-level block containing the currently troublesome |
d877f247 | 150 | block. |
c1ff6dac | 151 | - `next-line' -- Use the fact that, for an already hidden block, its end |
d877f247 | 152 | will be on the next line. Attempt to show this block. |
c1ff6dac TTN |
153 | - `signal' -- Pass the error through, stopping execution. |
154 | - `ignore' -- Ignore the error, continuing execution. | |
6da7653c | 155 | |
9479d258 | 156 | Values other than these four will be interpreted as `signal'.") |
6da7653c | 157 | |
23d93b6a | 158 | ;;;###autoload |
9479d258 | 159 | (defvar hs-special-modes-alist |
d06970e5 RS |
160 | '((c-mode "{" "}" nil nil hs-c-like-adjust-block-beginning) |
161 | (c++-mode "{" "}" "/[*/]" nil hs-c-like-adjust-block-beginning) | |
b6a386ad | 162 | (java-mode "\\(\\(\\([ \t]*\\(\\(abstract\\|final\\|native\\|p\\(r\\(ivate\\|otected\\)\\|ublic\\)\\|s\\(tatic\\|ynchronized\\)\\)[ \t\n]+\\)*[.a-zA-Z0-9_:]+[ \t\n]*\\(\\[[ \t\n]*\\][ \t\n]*\\)?\\([a-zA-Z0-9_:]+[ \t\n]*\\)([^)]*)\\([ \n\t]+throws[ \t\n][^{]+\\)?\\)\\|\\([ \t]*static[^{]*\\)\\)[ \t\n]*{\\)" "}" "/[*/]" java-hs-forward-sexp hs-c-like-adjust-block-beginning)) |
88039caa RS |
163 | ; I tested the java regexp using the following: |
164 | ;(defvar hsj-public) | |
88039caa RS |
165 | ;(defvar hsj-type) |
166 | ;(defvar hsj-fname) | |
167 | ;(defvar hsj-par) | |
168 | ;(defvar hsj-throws) | |
169 | ;(defvar hsj-static) | |
170 | ||
5e336d3e RS |
171 | ;(setq hsj-public |
172 | ; (concat "[ \t]*\\(" | |
173 | ; (regexp-opt '("public" "private" "protected" "abstract" | |
7ee90603 | 174 | ; "synchronized" "static" "final" "native") 1) |
b6a386ad | 175 | ; "[ \t\n]+\\)*")) |
5e336d3e | 176 | |
7ee90603 | 177 | ;(setq hsj-type "[.a-zA-Z0-9_:]+[ \t\n]*\\(\\[[ \t\n]*\\][ \t\n]*\\)?") |
88039caa RS |
178 | ;(setq hsj-fname "\\([a-zA-Z0-9_:]+[ \t\n]*\\)") |
179 | ;(setq hsj-par "([^)]*)") | |
180 | ;(setq hsj-throws "\\([ \n\t]+throws[ \t\n][^{]+\\)?") | |
181 | ||
182 | ;(setq hsj-static "[ \t]*static[^{]*") | |
183 | ||
184 | ||
185 | ;(setq hs-block-start-regexp (concat | |
186 | ; "\\(" | |
187 | ; "\\(" | |
188 | ; "\\(" | |
189 | ; hsj-public | |
88039caa RS |
190 | ; hsj-type |
191 | ; hsj-fname | |
192 | ; hsj-par | |
193 | ; hsj-throws | |
194 | ; "\\)" | |
195 | ; "\\|" | |
196 | ; "\\(" | |
197 | ; hsj-static | |
198 | ; "\\)" | |
199 | ; "\\)" | |
200 | ; "[ \t\n]*{" | |
201 | ; "\\)" | |
202 | ; )) | |
203 | ||
204 | "*Alist for initializing the hideshow variables for different modes. | |
205 | It has the form | |
206 | (MODE START-RE END-RE COMMENT-START-RE FORWARD-SEXP-FUNC ADJUST-BEG-FUNC). | |
c1ff6dac TTN |
207 | If present, hideshow will use these values for the start and end regexps, |
208 | respectively. Since Algol-ish languages do not have single-character | |
6da7653c | 209 | block delimiters, the function `forward-sexp' which is used by hideshow |
c1ff6dac TTN |
210 | doesn't work. In this case, if a similar function is provided, you can |
211 | register it and have hideshow use it instead of `forward-sexp'. To add | |
6da7653c TTN |
212 | more values, use |
213 | ||
214 | \t(pushnew '(new-mode st-re end-re function-name) | |
215 | \t hs-special-modes-alist :test 'equal) | |
216 | ||
c1ff6dac | 217 | For example: |
6da7653c | 218 | |
88039caa | 219 | \t(pushnew '(simula-mode \"begin\" \"end\" \"!\" simula-next-statement) |
6da7653c TTN |
220 | \t hs-special-modes-alist :test 'equal) |
221 | ||
88039caa RS |
222 | See the documentation for `hs-adjust-block-beginning' to see what |
223 | is the use of ADJUST-BEG-FUNC. | |
224 | ||
225 | If any of those is left nil, hideshow will try to guess some values, see | |
226 | `hs-grok-mode-type' for this. | |
227 | ||
c1ff6dac | 228 | Note that the regexps should not contain leading or trailing whitespace.") |
6da7653c | 229 | |
d877f247 RS |
230 | (defvar hs-hide-hook nil |
231 | "*Hooks called at the end of `hs-hide-all' and `hs-hide-block'.") | |
6da7653c | 232 | |
d877f247 RS |
233 | (defvar hs-show-hook nil |
234 | "*Hooks called at the end of commands to show text. | |
235 | These commands include `hs-show-all', `hs-show-block' and `hs-show-region'.") | |
6da7653c TTN |
236 | |
237 | (defvar hs-minor-mode-prefix "\C-c" | |
c1ff6dac | 238 | "*Prefix key to use for hideshow commands in hideshow minor mode.") |
6da7653c | 239 | |
6da7653c TTN |
240 | ;;;---------------------------------------------------------------------------- |
241 | ;;; internal variables | |
242 | ||
243 | (defvar hs-minor-mode nil | |
c1ff6dac TTN |
244 | "Non-nil if using hideshow mode as a minor mode of some other mode. |
245 | Use the command `hs-minor-mode' to toggle this variable.") | |
6da7653c TTN |
246 | |
247 | (defvar hs-minor-mode-map nil | |
c1ff6dac | 248 | "Mode map for hideshow minor mode.") |
6da7653c | 249 | |
88039caa RS |
250 | ;(defvar hs-menu-bar nil |
251 | ; "Menu bar for hideshow minor mode (Xemacs only).") | |
6da7653c TTN |
252 | |
253 | (defvar hs-c-start-regexp nil | |
9479d258 RS |
254 | "Regexp for beginning of comments. |
255 | Differs from mode-specific comment regexps in that | |
256 | surrounding whitespace is stripped.") | |
6da7653c | 257 | |
6da7653c | 258 | (defvar hs-block-start-regexp nil |
9479d258 | 259 | "Regexp for beginning of block.") |
6da7653c TTN |
260 | |
261 | (defvar hs-block-end-regexp nil | |
9479d258 | 262 | "Regexp for end of block.") |
6da7653c TTN |
263 | |
264 | (defvar hs-forward-sexp-func 'forward-sexp | |
9479d258 RS |
265 | "Function used to do a forward-sexp. |
266 | Should change for Algol-ish modes. For single-character block | |
267 | delimiters -- ie, the syntax table regexp for the character is | |
268 | either `(' or `)' -- `hs-forward-sexp-func' would just be `forward-sexp'. | |
269 | For other modes such as simula, a more specialized function | |
6da7653c TTN |
270 | is necessary.") |
271 | ||
88039caa RS |
272 | (defvar hs-adjust-block-beginning nil |
273 | "Function used to tweak the block beginning. | |
274 | It has effect only if `hs-show-hidden-short-form' is t. The block it | |
275 | is hidden from the point returned by this function, as opposed to | |
276 | hiding it from the point returned when searching | |
277 | `hs-block-start-regexp'. In c-like modes, if we wish to also hide the | |
278 | curly braces (if you think they occupy too much space on the screen), | |
279 | this function should return the starting point (at the end of line) of | |
280 | the hidden region. | |
281 | ||
282 | It is called with a single argument ARG which is the the position in | |
283 | buffer after the block beginning. | |
284 | ||
285 | It should return the position from where we should start hiding. | |
286 | ||
287 | It should not move the point. | |
288 | ||
d06970e5 | 289 | See `hs-c-like-adjust-block-beginning' for an example of using this.") |
c1ff6dac | 290 | |
9479d258 RS |
291 | ;(defvar hs-emacs-type 'fsf |
292 | ; "Used to support both Emacs and Xemacs.") | |
6da7653c | 293 | |
9479d258 RS |
294 | ;(eval-when-compile |
295 | ; (if (string-match "xemacs\\|lucid" emacs-version) | |
296 | ; (progn | |
297 | ; (defvar current-menubar nil "") | |
298 | ; (defun set-buffer-menubar (arg1)) | |
299 | ; (defun add-menu (arg1 arg2 arg3))))) | |
6da7653c TTN |
300 | |
301 | ;;;---------------------------------------------------------------------------- | |
302 | ;;; support funcs | |
303 | ||
88039caa | 304 | ;; snarfed from outline.el; |
6da7653c | 305 | (defun hs-flag-region (from to flag) |
12e36cdb RS |
306 | "Hides or shows lines from FROM to TO, according to FLAG. If FLAG |
307 | is nil then text is shown, while if FLAG is non-nil the text is | |
308 | hidden. Actualy flag is realy either `comment' or `block' depending on | |
309 | what kind of block it is suppose to hide." | |
9479d258 RS |
310 | (save-excursion |
311 | (goto-char from) | |
312 | (end-of-line) | |
313 | (hs-discard-overlays (point) to 'invisible 'hs) | |
314 | (if flag | |
315 | (let ((overlay (make-overlay (point) to))) | |
316 | ;; Make overlay hidden and intangible. | |
317 | (overlay-put overlay 'invisible 'hs) | |
318 | (overlay-put overlay 'hs t) | |
12e36cdb RS |
319 | (when (or (eq hs-isearch-open t) (eq hs-isearch-open flag)) |
320 | (overlay-put overlay 'isearch-open-invisible | |
321 | 'hs-isearch-open-invisible)) | |
9479d258 RS |
322 | (overlay-put overlay 'intangible t))))) |
323 | ||
12e36cdb RS |
324 | ;; This is set as an `isearch-open-invisible' property to hidden |
325 | ;; overlays. | |
326 | (defun hs-isearch-open-invisible (ov) | |
327 | (save-excursion | |
328 | (goto-char (overlay-start ov)) | |
329 | (hs-show-block))) | |
330 | ||
9479d258 RS |
331 | ;; Remove from the region BEG ... END all overlays |
332 | ;; with a PROP property equal to VALUE. | |
333 | ;; Overlays with a PROP property different from VALUE are not touched. | |
334 | (defun hs-discard-overlays (beg end prop value) | |
335 | (if (< end beg) | |
336 | (setq beg (prog1 end (setq end beg)))) | |
337 | (save-excursion | |
338 | (goto-char beg) | |
e863e264 RS |
339 | (let ((overlays (overlays-in beg end)) |
340 | o) | |
341 | (while overlays | |
342 | (setq o (car overlays)) | |
343 | (if (eq (overlay-get o prop) value) | |
344 | (delete-overlay o)) | |
345 | (setq overlays (cdr overlays)))))) | |
9479d258 | 346 | |
88039caa | 347 | (defun hs-hide-block-at-point (&optional end comment-reg) |
9479d258 | 348 | "Hide block iff on block beginning, optional END means reposition at end. |
88039caa RS |
349 | COMMENT-REG is a list of the form (BEGIN . END) and specifies the limits |
350 | of the comment, or nil if the block is not a comment." | |
351 | (if comment-reg | |
352 | (progn | |
353 | ;; goto the end of line at the end of the comment | |
354 | (goto-char (nth 1 comment-reg)) | |
355 | (unless hs-show-hidden-short-form (forward-line -1)) | |
9479d258 | 356 | (end-of-line) |
12e36cdb | 357 | (hs-flag-region (car comment-reg) (point) 'comment) |
88039caa | 358 | (goto-char (if end (nth 1 comment-reg) (car comment-reg)))) |
9479d258 | 359 | (if (looking-at hs-block-start-regexp) |
88039caa RS |
360 | (let* ((p ;; p is the point at the end of the block beginning |
361 | (if (and hs-show-hidden-short-form | |
362 | hs-adjust-block-beginning) | |
363 | ;; we need to adjust the block beginning | |
364 | (funcall hs-adjust-block-beginning (match-end 0)) | |
365 | (match-end 0))) | |
366 | ;; q is the point at the end of the block | |
9479d258 | 367 | (q (progn (funcall hs-forward-sexp-func 1) (point)))) |
88039caa RS |
368 | ;; position the point so we can call `hs-flag-region' |
369 | (unless hs-show-hidden-short-form (forward-line -1)) | |
370 | (end-of-line) | |
371 | (if (and (< p (point)) (> (count-lines p q) | |
372 | (if hs-show-hidden-short-form 1 2))) | |
12e36cdb | 373 | (hs-flag-region p (point) 'block)) |
88039caa RS |
374 | (goto-char (if end q p)))))) |
375 | ||
376 | (defun hs-show-block-at-point (&optional end comment-reg) | |
377 | "Show block iff on block beginning. Optional END means reposition at end. | |
378 | COMMENT-REG is a list of the forme (BEGIN . END) and specifies the limits | |
379 | of the comment. It should be nil when hiding a block." | |
380 | (if comment-reg | |
381 | (when (car comment-reg) | |
382 | (hs-flag-region (car comment-reg) (nth 1 comment-reg) nil) | |
383 | (goto-char (if end (nth 1 comment-reg) (car comment-reg)))) | |
384 | (if (looking-at hs-block-start-regexp) | |
385 | (let* ((p (point)) | |
386 | (q | |
387 | (condition-case error ; probably unbalanced paren | |
388 | (progn | |
389 | (funcall hs-forward-sexp-func 1) | |
390 | (point)) | |
391 | (error | |
392 | (cond | |
393 | ((eq hs-unbalance-handler-method 'ignore) | |
394 | ;; just ignore this block | |
395 | (point)) | |
396 | ((eq hs-unbalance-handler-method 'top-level) | |
397 | ;; try to get out of rat's nest and expose the whole func | |
398 | (if (/= (current-column) 0) (beginning-of-defun)) | |
399 | (setq p (point)) | |
400 | (re-search-forward (concat "^" hs-block-start-regexp) | |
401 | (point-max) t 2) | |
402 | (point)) | |
403 | ((eq hs-unbalance-handler-method 'next-line) | |
404 | ;; assumption is that user knows what s/he's doing | |
405 | (beginning-of-line) (setq p (point)) | |
406 | (end-of-line 2) (point)) | |
407 | (t | |
408 | ;; pass error through -- this applies to `signal', too | |
409 | (signal (car error) (cdr error)))))))) | |
410 | (hs-flag-region p q nil) | |
411 | (goto-char (if end (1+ (point)) p)))))) | |
6da7653c TTN |
412 | |
413 | (defun hs-safety-is-job-n () | |
9479d258 | 414 | "Warn `buffer-invisibility-spec' does not contain hs." |
88039caa | 415 | (if (or buffer-invisibility-spec (assq 'hs buffer-invisibility-spec) ) |
c1ff6dac | 416 | nil |
9479d258 RS |
417 | (message "Warning: `buffer-invisibility-spec' does not contain hs!!") |
418 | (sit-for 2))) | |
419 | ||
420 | (defun hs-hide-initial-comment-block () | |
421 | (interactive) | |
422 | "Hides the first block of comments in a file. | |
423 | The best usage is in `hs-minor-mode-hook', it hides all the comments at the | |
424 | file beginning, so if you have huge RCS logs you won't see them!" | |
425 | (let ((p (point)) | |
426 | c-reg) | |
427 | (goto-char (point-min)) | |
88039caa | 428 | (skip-chars-forward " \t\n^L") |
9479d258 | 429 | (setq c-reg (hs-inside-comment-p)) |
88039caa RS |
430 | ;; see if we have enough comment lines to hide |
431 | (if (and c-reg (> (count-lines (car c-reg) (nth 1 c-reg)) | |
432 | (if hs-show-hidden-short-form 1 2))) | |
9479d258 RS |
433 | (hs-hide-block) |
434 | (goto-char p)))) | |
435 | ||
6da7653c | 436 | (defun hs-inside-comment-p () |
c1ff6dac | 437 | "Returns non-nil if point is inside a comment, otherwise nil. |
9479d258 | 438 | Actually, returns a list containing the buffer position of the start |
88039caa RS |
439 | and the end of the comment. A comment block can be hided only if on its |
440 | starting line there are only white spaces preceding the actual comment | |
441 | beginning, if we are inside of a comment but this condition is not | |
442 | we return a list having a nil as its car and the end of comment position | |
443 | as cdr." | |
444 | (save-excursion | |
445 | ;; the idea is to look backwards for a comment start regexp, do a | |
446 | ;; forward comment, and see if we are inside, then extend extend | |
447 | ;; forward and backward as long as we have comments | |
448 | (let ((q (point))) | |
449 | (when (or (looking-at hs-c-start-regexp) | |
450 | (re-search-backward hs-c-start-regexp (point-min) t)) | |
451 | (forward-comment (- (buffer-size))) | |
452 | (skip-chars-forward " \t\n\f") | |
453 | (let ((p (point)) | |
454 | (not-hidable nil)) | |
455 | (beginning-of-line) | |
456 | (unless (looking-at (concat "[ \t]*" hs-c-start-regexp)) | |
457 | ;; we are in this situation: (example) | |
458 | ;; (defun bar () | |
459 | ;; (foo) | |
460 | ;; ) ; comment | |
461 | ;; ^ | |
462 | ;; the point was here before doing (beginning-of-line) | |
463 | ;; here we should advance till the next comment which | |
464 | ;; eventually has only white spaces preceding it on the same | |
465 | ;; line | |
466 | (goto-char p) | |
467 | (forward-comment 1) | |
468 | (skip-chars-forward " \t\n\f") | |
469 | (setq p (point)) | |
470 | (while (and (< (point) q) | |
471 | (> (point) p) | |
472 | (not (looking-at hs-c-start-regexp))) | |
473 | (setq p (point)) ;; use this to avoid an infinit cycle. | |
474 | (forward-comment 1) | |
475 | (skip-chars-forward " \t\n\f")) | |
476 | (if (or (not (looking-at hs-c-start-regexp)) | |
477 | (> (point) q)) | |
478 | ;; we cannot hide this comment block | |
479 | (setq not-hidable t))) | |
480 | ;; goto the end of the comment | |
481 | (forward-comment (buffer-size)) | |
482 | (skip-chars-backward " \t\n\f") | |
483 | (end-of-line) | |
484 | (if (>= (point) q) | |
485 | (list (if not-hidable nil p) (point)))))))) | |
6da7653c TTN |
486 | |
487 | (defun hs-grok-mode-type () | |
c1ff6dac | 488 | "Setup variables for new buffers where applicable." |
88039caa RS |
489 | (when (and (boundp 'comment-start) |
490 | (boundp 'comment-end)) | |
491 | (let ((lookup (assoc major-mode hs-special-modes-alist))) | |
492 | (setq hs-block-start-regexp (or (nth 1 lookup) "\\s\(") | |
493 | hs-block-end-regexp (or (nth 2 lookup) "\\s\)") | |
494 | hs-c-start-regexp (or (nth 3 lookup) | |
495 | (let ((c-start-regexp | |
496 | (regexp-quote comment-start))) | |
497 | (if (string-match " +$" c-start-regexp) | |
498 | (substring c-start-regexp 0 (1- (match-end 0))) | |
499 | c-start-regexp))) | |
500 | hs-forward-sexp-func (or (nth 4 lookup) 'forward-sexp) | |
501 | hs-adjust-block-beginning (nth 5 lookup))))) | |
6da7653c TTN |
502 | |
503 | (defun hs-find-block-beginning () | |
88039caa RS |
504 | "Repositions point at block-start. |
505 | Return point, or nil if top-level." | |
6da7653c | 506 | (let (done |
88039caa | 507 | (try-again t) |
6da7653c TTN |
508 | (here (point)) |
509 | (both-regexps (concat "\\(" hs-block-start-regexp "\\)\\|\\(" | |
b7c09257 RS |
510 | hs-block-end-regexp "\\)")) |
511 | (buf-size (buffer-size))) | |
88039caa RS |
512 | (beginning-of-line) |
513 | ;; A block beginning can span on multiple lines, if the point | |
514 | ;; is on one of those lines, trying a regexp search from | |
515 | ;; that point would fail to find the block beginning, so we look | |
516 | ;; backwards for the block beginning, or a block end. | |
517 | (while try-again | |
518 | (setq try-again nil) | |
d06970e5 RS |
519 | (if (and (re-search-backward both-regexps (point-min) t) |
520 | (match-beginning 1)) ; found a block beginning | |
521 | (if (save-match-data (hs-inside-comment-p)) | |
522 | ;;but it was inside a comment, so we have to look for | |
523 | ;;it again | |
524 | (setq try-again t) | |
525 | ;; that's what we were looking for | |
526 | (setq done (match-beginning 0))) | |
527 | ;; we found a block end, or we reached the beginning of the | |
528 | ;; buffer look to see if we were on a block beginning when we | |
529 | ;; started | |
530 | (if (and | |
531 | (re-search-forward hs-block-start-regexp (point-max) t) | |
532 | (or | |
533 | (and (>= here (match-beginning 0)) (< here (match-end 0))) | |
534 | (and hs-show-hidden-short-form hs-adjust-block-beginning | |
535 | (save-match-data | |
536 | (= 1 (count-lines | |
537 | (funcall hs-adjust-block-beginning | |
538 | (match-end 0)) here)))))) | |
539 | (setq done (match-beginning 0))))) | |
88039caa | 540 | (goto-char here) |
6da7653c | 541 | (while (and (not done) |
88039caa RS |
542 | ;; This had problems because the regexp can match something |
543 | ;; inside of a comment! | |
544 | ;; Since inside a comment we can have incomplete sexps | |
545 | ;; this would have signaled an error. | |
b7c09257 | 546 | (or (forward-comment (- buf-size)) t); `or' is a hack to |
88039caa | 547 | ; make it return t |
6da7653c TTN |
548 | (re-search-backward both-regexps (point-min) t)) |
549 | (if (match-beginning 1) ; start of start-regexp | |
88039caa RS |
550 | (setq done (match-beginning 0)) |
551 | (goto-char (match-end 0)) ; end of end-regexp | |
6da7653c TTN |
552 | (funcall hs-forward-sexp-func -1))) |
553 | (goto-char (or done here)) | |
554 | done)) | |
555 | ||
556 | (defmacro hs-life-goes-on (&rest body) | |
c1ff6dac | 557 | "Executes optional BODY iff variable `hs-minor-mode' is non-nil." |
5e336d3e RS |
558 | (` (let ((inhibit-point-motion-hooks t)) |
559 | (when hs-minor-mode | |
560 | (,@ body))))) | |
561 | ||
562 | (put 'hs-life-goes-on 'edebug-form-spec '(&rest form)) | |
6da7653c | 563 | |
9479d258 | 564 | (defun hs-already-hidden-p () |
88039caa | 565 | "Return non-nil if point is in an already-hidden block, otherwise nil." |
9479d258 | 566 | (save-excursion |
88039caa RS |
567 | (let ((c-reg (hs-inside-comment-p))) |
568 | (if (and c-reg (nth 0 c-reg)) | |
569 | ;; point is inside a comment, and that comment is hidable | |
570 | (goto-char (nth 0 c-reg)) | |
571 | (if (and (not c-reg) (hs-find-block-beginning) | |
572 | (looking-at hs-block-start-regexp)) | |
573 | ;; point is inside a block | |
574 | (goto-char (match-end 0))))) | |
9479d258 RS |
575 | (end-of-line) |
576 | (let ((overlays (overlays-at (point))) | |
577 | (found nil)) | |
578 | (while (and (not found) (overlayp (car overlays))) | |
579 | (setq found (overlay-get (car overlays) 'hs) | |
580 | overlays (cdr overlays))) | |
581 | found))) | |
582 | ||
583 | (defun java-hs-forward-sexp (arg) | |
584 | "Function used by `hs-minor-mode' for `forward-sexp' in Java mode." | |
585 | (if (< arg 0) | |
586 | (backward-sexp 1) | |
587 | (if (looking-at hs-block-start-regexp) | |
588 | (progn | |
589 | (goto-char (match-end 0)) | |
590 | (forward-char -1) | |
591 | (forward-sexp 1)) | |
592 | (forward-sexp 1)))) | |
6da7653c | 593 | |
d06970e5 RS |
594 | (defun hs-c-like-adjust-block-beginning (arg) |
595 | "Function to be assigned to `hs-adjust-block-beginning' for C like modes. | |
88039caa RS |
596 | Arg is a position in buffer just after {. This goes back to the end of |
597 | the function header. The purpose is to save some space on the screen | |
598 | when displaying hidden blocks." | |
599 | (save-excursion | |
600 | (goto-char arg) | |
601 | (forward-char -1) | |
602 | (forward-comment (- (buffer-size))) | |
603 | (point))) | |
604 | ||
6da7653c TTN |
605 | ;;;---------------------------------------------------------------------------- |
606 | ;;; commands | |
607 | ||
608 | ;;;###autoload | |
609 | (defun hs-hide-all () | |
c1ff6dac | 610 | "Hides all top-level blocks, displaying only first and last lines. |
d877f247 | 611 | It moves point to the beginning of the line, and it runs the normal hook |
9479d258 RS |
612 | `hs-hide-hook'. See documentation for `run-hooks'. |
613 | If `hs-hide-comments-when-hiding-all' is t also hides the comments." | |
6da7653c TTN |
614 | (interactive) |
615 | (hs-life-goes-on | |
9479d258 | 616 | (message "Hiding all blocks ...") |
6da7653c | 617 | (save-excursion |
9479d258 | 618 | (hs-flag-region (point-min) (point-max) nil) ; eliminate weirdness |
6da7653c | 619 | (goto-char (point-min)) |
9479d258 | 620 | (if hs-hide-comments-when-hiding-all |
88039caa RS |
621 | (let (c-reg |
622 | (count 0) | |
9479d258 RS |
623 | (block-and-comment-re ;; this should match |
624 | (concat "\\(^" ;; the block beginning and comment start | |
625 | hs-block-start-regexp | |
626 | "\\)\\|\\(" hs-c-start-regexp "\\)"))) | |
627 | (while (re-search-forward block-and-comment-re (point-max) t) | |
628 | (if (match-beginning 1) ;; we have found a block beginning | |
629 | (progn | |
630 | (goto-char (match-beginning 1)) | |
631 | (hs-hide-block-at-point t) | |
632 | (message "Hiding ... %d" (setq count (1+ count)))) | |
633 | ;;found a comment | |
634 | (setq c-reg (hs-inside-comment-p)) | |
88039caa RS |
635 | (if (and c-reg (car c-reg)) |
636 | (if (> (count-lines (car c-reg) (nth 1 c-reg)) | |
637 | (if hs-show-hidden-short-form 1 2)) | |
638 | (progn | |
639 | (hs-hide-block-at-point t c-reg) | |
640 | (message "Hiding ... %d" (setq count (1+ count)))) | |
641 | (goto-char (nth 1 c-reg))))))) | |
9479d258 | 642 | (let ((count 0) |
b7c09257 RS |
643 | (top-level-re (concat "^" hs-block-start-regexp)) |
644 | (buf-size (buffer-size))) | |
9479d258 RS |
645 | (while |
646 | (progn | |
b7c09257 | 647 | (forward-comment buf-size) |
9479d258 RS |
648 | (re-search-forward top-level-re (point-max) t)) |
649 | (goto-char (match-beginning 0)) | |
650 | (hs-hide-block-at-point t) | |
651 | (message "Hiding ... %d" (setq count (1+ count)))))) | |
652 | (hs-safety-is-job-n)) | |
6da7653c | 653 | (beginning-of-line) |
9479d258 | 654 | (message "Hiding all blocks ... done") |
d877f247 | 655 | (run-hooks 'hs-hide-hook))) |
6da7653c TTN |
656 | |
657 | (defun hs-show-all () | |
c1ff6dac | 658 | "Shows all top-level blocks. |
d877f247 RS |
659 | This does not change point; it runs the normal hook `hs-show-hook'. |
660 | See documentation for `run-hooks'." | |
6da7653c TTN |
661 | (interactive) |
662 | (hs-life-goes-on | |
9479d258 RS |
663 | (message "Showing all blocks ...") |
664 | (hs-flag-region (point-min) (point-max) nil) | |
665 | (message "Showing all blocks ... done") | |
d877f247 | 666 | (run-hooks 'hs-show-hook))) |
6da7653c | 667 | |
6da7653c | 668 | (defun hs-hide-block (&optional end) |
9479d258 | 669 | "Selects a block and hides it. |
88039caa RS |
670 | With prefix arg, reposition at end. Block is defined as a sexp for |
671 | lispish modes, mode-specific otherwise. Comments are blocks, too. | |
672 | Upon completion, point is at repositioned and the normal hook | |
9479d258 | 673 | `hs-hide-hook' is run. See documentation for `run-hooks'." |
6da7653c TTN |
674 | (interactive "P") |
675 | (hs-life-goes-on | |
676 | (let ((c-reg (hs-inside-comment-p))) | |
88039caa RS |
677 | (cond |
678 | ((and c-reg (or (null (nth 0 c-reg)) | |
679 | (<= (count-lines (car c-reg) (nth 1 c-reg)) | |
680 | (if hs-show-hidden-short-form 1 2)))) | |
9479d258 | 681 | (message "Not enough comment lines to hide!")) |
88039caa | 682 | ((or c-reg (looking-at hs-block-start-regexp) |
c1ff6dac | 683 | (hs-find-block-beginning)) |
88039caa RS |
684 | (hs-hide-block-at-point end c-reg) |
685 | (hs-safety-is-job-n) | |
686 | (run-hooks 'hs-hide-hook)))))) | |
6da7653c TTN |
687 | |
688 | (defun hs-show-block (&optional end) | |
9479d258 | 689 | "Selects a block and shows it. |
88039caa | 690 | With prefix arg, reposition at end. Upon completion, point is |
9479d258 RS |
691 | repositioned and the normal hook `hs-show-hook' is run. |
692 | See documentation for `hs-hide-block' and `run-hooks'." | |
6da7653c TTN |
693 | (interactive "P") |
694 | (hs-life-goes-on | |
695 | (let ((c-reg (hs-inside-comment-p))) | |
88039caa RS |
696 | (if (or c-reg |
697 | (looking-at hs-block-start-regexp) | |
698 | (hs-find-block-beginning)) | |
c1ff6dac | 699 | (progn |
88039caa | 700 | (hs-show-block-at-point end c-reg) |
c1ff6dac | 701 | (hs-safety-is-job-n) |
88039caa | 702 | (run-hooks 'hs-show-hook)))))) |
6da7653c TTN |
703 | |
704 | (defun hs-show-region (beg end) | |
c1ff6dac | 705 | "Shows all lines from BEG to END, without doing any block analysis. |
9479d258 | 706 | Note:`hs-show-region' is intended for use when `hs-show-block' signals |
c1ff6dac | 707 | `unbalanced parentheses' and so is an emergency measure only. You may |
6da7653c TTN |
708 | become very confused if you use this command indiscriminately." |
709 | (interactive "r") | |
710 | (hs-life-goes-on | |
9479d258 | 711 | (hs-flag-region beg end nil) |
6da7653c | 712 | (hs-safety-is-job-n) |
d877f247 | 713 | (run-hooks 'hs-show-hook))) |
6da7653c | 714 | |
9479d258 RS |
715 | ;;;###autoload |
716 | (defun hs-mouse-toggle-hiding (e) | |
717 | "Toggles hiding/showing of a block. | |
718 | Should be bound to a mouse key." | |
719 | (interactive "@e") | |
720 | (mouse-set-point e) | |
721 | (if (hs-already-hidden-p) | |
722 | (hs-show-block) | |
723 | (hs-hide-block))) | |
724 | ||
6da7653c TTN |
725 | ;;;###autoload |
726 | (defun hs-minor-mode (&optional arg) | |
c1ff6dac TTN |
727 | "Toggle hideshow minor mode. |
728 | With ARG, turn hideshow minor mode on if ARG is positive, off otherwise. | |
729 | When hideshow minor mode is on, the menu bar is augmented with hideshow | |
88039caa RS |
730 | commands and the hideshow commands are enabled. |
731 | The value '(hs . t) is added to `buffer-invisibility-spec'. | |
9479d258 RS |
732 | Last, the normal hook `hs-minor-mode-hook' is run; see the doc |
733 | for `run-hooks'. | |
d877f247 | 734 | |
88039caa RS |
735 | The main commands are: `hs-hide-all', `hs-show-all', `hs-hide-block' |
736 | and `hs-show-block'. | |
737 | Also see the documentation for the variable `hs-show-hidden-short-form'. | |
738 | ||
c1ff6dac | 739 | Turning hideshow minor mode off reverts the menu bar and the |
88039caa RS |
740 | variables to default values and disables the hideshow commands. |
741 | ||
742 | Key bindings: | |
743 | \\{hs-minor-mode-map}" | |
744 | ||
6da7653c TTN |
745 | (interactive "P") |
746 | (setq hs-minor-mode | |
747 | (if (null arg) | |
748 | (not hs-minor-mode) | |
749 | (> (prefix-numeric-value arg) 0))) | |
750 | (if hs-minor-mode | |
751 | (progn | |
9479d258 RS |
752 | ; (if (eq hs-emacs-type 'lucid) |
753 | ; (progn | |
754 | ; (set-buffer-menubar (copy-sequence current-menubar)) | |
755 | ; (add-menu nil (car hs-menu-bar) (cdr hs-menu-bar)))) | |
756 | (make-variable-buffer-local 'line-move-ignore-invisible) | |
757 | (setq line-move-ignore-invisible t) | |
758 | (add-to-invisibility-spec '(hs . t)) ;;hs invisible | |
6da7653c TTN |
759 | (hs-grok-mode-type) |
760 | (run-hooks 'hs-minor-mode-hook)) | |
9479d258 RS |
761 | ; (if (eq hs-emacs-type 'lucid) |
762 | ; (set-buffer-menubar (delete hs-menu-bar current-menubar))) | |
763 | (remove-from-invisibility-spec '(hs . t)))) | |
6da7653c TTN |
764 | |
765 | ||
766 | ;;;---------------------------------------------------------------------------- | |
767 | ;;; load-time setup routines | |
768 | ||
c1ff6dac | 769 | ;; which emacs being used? |
9479d258 RS |
770 | ;(setq hs-emacs-type |
771 | ; (if (string-match "xemacs\\|lucid" emacs-version) | |
772 | ; 'lucid | |
773 | ; 'fsf)) | |
c1ff6dac | 774 | |
6da7653c | 775 | ;; keymaps and menus |
9479d258 RS |
776 | (if hs-minor-mode-map |
777 | nil | |
778 | (setq hs-minor-mode-map (make-sparse-keymap)) | |
779 | ;; I beleive there is nothing bound on this keys | |
780 | (define-key hs-minor-mode-map "\C-ch" 'hs-hide-block) | |
781 | (define-key hs-minor-mode-map "\C-cs" 'hs-show-block) | |
782 | (define-key hs-minor-mode-map "\C-cH" 'hs-hide-all) | |
783 | (define-key hs-minor-mode-map "\C-cS" 'hs-show-all) | |
784 | (define-key hs-minor-mode-map "\C-cR" 'hs-show-region) | |
785 | ||
786 | (define-key hs-minor-mode-map [S-mouse-2] 'hs-mouse-toggle-hiding) | |
787 | ||
788 | ;; should we use easymenu here? | |
789 | (define-key hs-minor-mode-map [menu-bar Hide/Show] | |
790 | (cons "Hide/Show" (make-sparse-keymap "Hide/Show"))) | |
791 | (define-key hs-minor-mode-map [menu-bar Hide/Show hs-show-region] | |
792 | '("Show Region" . hs-show-region)) | |
793 | (define-key hs-minor-mode-map [menu-bar Hide/Show hs-show-all] | |
794 | '("Show All" . hs-show-all)) | |
795 | (define-key hs-minor-mode-map [menu-bar Hide/Show hs-hide-all] | |
796 | '("Hide All" . hs-hide-all)) | |
797 | (define-key hs-minor-mode-map [menu-bar Hide/Show hs-show-block] | |
798 | '("Show Block" . hs-show-block)) | |
799 | (define-key hs-minor-mode-map [menu-bar Hide/Show hs-hide-block] | |
88039caa | 800 | '("Hide Block" . hs-hide-block))) |
6da7653c TTN |
801 | |
802 | ;; some housekeeping | |
c1ff6dac TTN |
803 | (or (assq 'hs-minor-mode minor-mode-map-alist) |
804 | (setq minor-mode-map-alist | |
805 | (cons (cons 'hs-minor-mode hs-minor-mode-map) | |
806 | minor-mode-map-alist))) | |
807 | (or (assq 'hs-minor-mode minor-mode-alist) | |
808 | (setq minor-mode-alist (append minor-mode-alist | |
809 | (list '(hs-minor-mode " hs"))))) | |
6da7653c TTN |
810 | |
811 | ;; make some variables buffer-local | |
812 | (make-variable-buffer-local 'hs-minor-mode) | |
813 | (make-variable-buffer-local 'hs-c-start-regexp) | |
6da7653c TTN |
814 | (make-variable-buffer-local 'hs-block-start-regexp) |
815 | (make-variable-buffer-local 'hs-block-end-regexp) | |
816 | (make-variable-buffer-local 'hs-forward-sexp-func) | |
88039caa | 817 | (make-variable-buffer-local 'hs-adjust-block-beginning) |
6da7653c TTN |
818 | (put 'hs-minor-mode 'permanent-local t) |
819 | (put 'hs-c-start-regexp 'permanent-local t) | |
6da7653c TTN |
820 | (put 'hs-block-start-regexp 'permanent-local t) |
821 | (put 'hs-block-end-regexp 'permanent-local t) | |
822 | (put 'hs-forward-sexp-func 'permanent-local t) | |
88039caa | 823 | (put 'hs-adjust-block-beginning 'permanent-local t) |
6da7653c TTN |
824 | |
825 | ||
826 | ;;;---------------------------------------------------------------------------- | |
827 | ;;; that's it | |
828 | ||
829 | (provide 'hideshow) | |
830 | ||
831 | ;;; hideshow.el ends here |