Mercurial > emacs
comparison lisp/textmodes/outline.el @ 41657:4f8e9cc04af5
(outline-up-heading): Add `invisible-ok' arg.
(outline-up-heading-all): Remove.
(hide-sublevels): Move to end-of-heading before calling flag-region.
(outline-copy-overlay, outline-discard-overlays): Remove.
(outline-flag-region): Use `remove-overlays'.
Don't move to end-of-heading.
(outline-next-visible-heading, outline-back-to-heading)
(outline-on-heading-p): Use outline-invisible-p.
(outline-font-lock-level): Use outline-up-heading's new arg.
(outline-minor-mode): Simplify.
(outline-map-tree, outline-reveal-toggle-invisible): New funs.
(outline): Put a `reveal-toggle-invisible' property.
(outline-level-heading): New var.
(outline-insert-heading, outline-promote, outline-demote)
(outline-toggle-children): New commands.
author | Stefan Monnier <monnier@iro.umontreal.ca> |
---|---|
date | Thu, 29 Nov 2001 02:15:03 +0000 |
parents | fc133c103640 |
children | 83b107455579 |
comparison
equal
deleted
inserted
replaced
41656:ec668c07bca2 | 41657:4f8e9cc04af5 |
---|---|
30 ;; with all stuff below hidden. See the Emacs manual for details. | 30 ;; with all stuff below hidden. See the Emacs manual for details. |
31 | 31 |
32 ;;; Todo: | 32 ;;; Todo: |
33 | 33 |
34 ;; - subtree-terminators | 34 ;; - subtree-terminators |
35 ;; - better handle comments before function bodies (i.e. heading) | |
36 ;; - don't bother hiding whitespace | |
35 | 37 |
36 ;;; Code: | 38 ;;; Code: |
37 | 39 |
38 (defgroup outlines nil | 40 (defgroup outlines nil |
39 "Support for hierarchical outlining" | 41 "Support for hierarchical outlining" |
145 (defvar outline-font-lock-keywords | 147 (defvar outline-font-lock-keywords |
146 '(;; | 148 '(;; |
147 ;; Highlight headings according to the level. | 149 ;; Highlight headings according to the level. |
148 (eval . (list (concat "^" outline-regexp ".+") | 150 (eval . (list (concat "^" outline-regexp ".+") |
149 0 '(or (cdr (assq (outline-font-lock-level) | 151 0 '(or (cdr (assq (outline-font-lock-level) |
152 ;; FIXME: this is silly! | |
150 '((1 . font-lock-function-name-face) | 153 '((1 . font-lock-function-name-face) |
151 (2 . font-lock-variable-name-face) | 154 (2 . font-lock-variable-name-face) |
152 (3 . font-lock-keyword-face) | 155 (3 . font-lock-keyword-face) |
153 (4 . font-lock-builtin-face) | 156 (4 . font-lock-builtin-face) |
154 (5 . font-lock-comment-face) | 157 (5 . font-lock-comment-face) |
163 (let ((count 1)) | 166 (let ((count 1)) |
164 (save-excursion | 167 (save-excursion |
165 (outline-back-to-heading t) | 168 (outline-back-to-heading t) |
166 (while (and (not (bobp)) | 169 (while (and (not (bobp)) |
167 (not (eq (funcall outline-level) 1))) | 170 (not (eq (funcall outline-level) 1))) |
168 (outline-up-heading-all 1) | 171 (outline-up-heading 1 t) |
169 (setq count (1+ count))) | 172 (setq count (1+ count))) |
170 count))) | 173 count))) |
171 | 174 |
172 (defvar outline-view-change-hook nil | 175 (defvar outline-view-change-hook nil |
173 "Normal hook to be run after outline visibility changes.") | 176 "Normal hook to be run after outline visibility changes.") |
251 (set (make-local-variable 'line-move-ignore-invisible) t) | 254 (set (make-local-variable 'line-move-ignore-invisible) t) |
252 ;; Cause use of ellipses for invisible text. | 255 ;; Cause use of ellipses for invisible text. |
253 (add-to-invisibility-spec '(outline . t))) | 256 (add-to-invisibility-spec '(outline . t))) |
254 (setq line-move-ignore-invisible nil) | 257 (setq line-move-ignore-invisible nil) |
255 ;; Cause use of ellipses for invisible text. | 258 ;; Cause use of ellipses for invisible text. |
256 (remove-from-invisibility-spec '(outline . t))) | 259 (remove-from-invisibility-spec '(outline . t)) |
257 ;; When turning off outline mode, get rid of any outline hiding. | 260 ;; When turning off outline mode, get rid of any outline hiding. |
258 (or outline-minor-mode | 261 (show-all))) |
259 (show-all))) | |
260 | 262 |
261 (defcustom outline-level 'outline-level | 263 (defcustom outline-level 'outline-level |
262 "*Function of no args to compute a header's nesting level in an outline. | 264 "*Function of no args to compute a header's nesting level in an outline. |
263 It can assume point is at the beginning of a header line." | 265 It can assume point is at the beginning of a header line." |
264 :type 'function | 266 :type 'function |
316 (save-excursion | 318 (save-excursion |
317 (while (not found) | 319 (while (not found) |
318 (or (re-search-backward (concat "^\\(" outline-regexp "\\)") | 320 (or (re-search-backward (concat "^\\(" outline-regexp "\\)") |
319 nil t) | 321 nil t) |
320 (error "before first heading")) | 322 (error "before first heading")) |
321 (setq found (and (or invisible-ok (outline-visible)) (point))))) | 323 (setq found (and (or invisible-ok (not (outline-invisible-p))) |
324 (point))))) | |
322 (goto-char found) | 325 (goto-char found) |
323 found))) | 326 found))) |
324 | 327 |
325 (defun outline-on-heading-p (&optional invisible-ok) | 328 (defun outline-on-heading-p (&optional invisible-ok) |
326 "Return t if point is on a (visible) heading line. | 329 "Return t if point is on a (visible) heading line. |
327 If INVISIBLE-OK is non-nil, an invisible heading line is ok too." | 330 If INVISIBLE-OK is non-nil, an invisible heading line is ok too." |
328 (save-excursion | 331 (save-excursion |
329 (beginning-of-line) | 332 (beginning-of-line) |
330 (and (bolp) (or invisible-ok (outline-visible)) | 333 (and (bolp) (or invisible-ok (not (outline-invisible-p))) |
331 (looking-at outline-regexp)))) | 334 (looking-at outline-regexp)))) |
335 | |
336 (defvar outline-level-heading () | |
337 "Alist associating a heading for every possible level.") | |
338 (make-variable-buffer-local 'outline-level-heading) | |
339 | |
340 (defun outline-insert-heading () | |
341 "Insert a new heading at same depth at point." | |
342 (interactive) | |
343 (let ((head (save-excursion | |
344 (condition-case nil | |
345 (outline-back-to-heading) | |
346 (error (outline-next-heading))) | |
347 (if (eobp) | |
348 (or (cdar outline-level-heading) "") | |
349 (match-string 0))))) | |
350 (unless (or (string-match "[ \t]\\'" head) | |
351 (not (string-match outline-regexp (concat head " ")))) | |
352 (setq head (concat head " "))) | |
353 (unless (bolp) (end-of-line) (newline)) | |
354 (insert head) | |
355 (unless (eolp) | |
356 (save-excursion (newline-and-indent))) | |
357 (run-hooks 'outline-insert-heading-hook))) | |
358 | |
359 (defun outline-promote (&optional children) | |
360 "Promote the current heading higher up the tree. | |
361 If prefix argument CHILDREN is given, promote also all the children." | |
362 (interactive "P") | |
363 (outline-back-to-heading) | |
364 (let* ((head (match-string 0)) | |
365 (level (save-match-data (funcall outline-level))) | |
366 (up-head (or (cdr (assoc head outline-level-headings)) | |
367 (cdr (assoc (1- level) outline-level-headings)) | |
368 (save-excursion | |
369 (save-match-data | |
370 (outline-up-heading 1 t) | |
371 (match-string 0)))))) | |
372 | |
373 (unless (assoc level outline-level-headings) | |
374 (push (cons level head) outline-level-headings)) | |
375 | |
376 (replace-match up-head nil t) | |
377 (when children | |
378 (outline-map-tree 'outline-promote level)))) | |
379 | |
380 (defun outline-demote (&optional children) | |
381 "Demote the current heading lower down the tree. | |
382 If prefix argument CHILDREN is given, demote also all the children." | |
383 (interactive "P") | |
384 (outline-back-to-heading) | |
385 (let* ((head (match-string 0)) | |
386 (level (save-match-data (funcall outline-level))) | |
387 (down-head | |
388 (or (let ((x (car (rassoc head outline-level-headings)))) | |
389 (if (stringp x) x)) | |
390 (cdr (assoc (1+ level) outline-level-headings)) | |
391 (save-excursion | |
392 (save-match-data | |
393 (while (and (not (eobp)) | |
394 (progn | |
395 (outline-next-heading) | |
396 (<= (funcall outline-level) level)))) | |
397 (when (eobp) | |
398 ;; Try again from the beginning of the buffer. | |
399 (goto-char (point-min)) | |
400 (while (and (not (eobp)) | |
401 (progn | |
402 (outline-next-heading) | |
403 (<= (funcall outline-level) level))))) | |
404 (unless (eobp) (match-string 0)))) | |
405 (save-match-data | |
406 ;; Bummer!! There is no lower heading in the buffer. | |
407 ;; Let's try to invent one by repeating the first char. | |
408 (let ((new-head (concat (substring head 0 1) head))) | |
409 (if (string-match (concat "\\`" outline-regexp) new-head) | |
410 ;; Why bother checking that it is indeed of lower level ? | |
411 new-head | |
412 ;; Didn't work: keep it as is so it's still a heading. | |
413 head)))))) | |
414 | |
415 (unless (assoc level outline-level-headings) | |
416 (push (cons level head) outline-level-headings)) | |
417 | |
418 (replace-match down-head nil t) | |
419 (when children | |
420 (outline-map-tree 'outline-demote level)))) | |
421 | |
422 (defun outline-map-tree (fun level) | |
423 "Call FUN for every heading underneath the current one." | |
424 (save-excursion | |
425 (while (and (progn | |
426 (outline-next-heading) | |
427 (> (funcall outline-level) level)) | |
428 (not (eobp))) | |
429 (funcall fun)))) | |
332 | 430 |
333 (defun outline-end-of-heading () | 431 (defun outline-end-of-heading () |
334 (if (re-search-forward outline-heading-end-regexp nil 'move) | 432 (if (re-search-forward outline-heading-end-regexp nil 'move) |
335 (forward-char -1))) | 433 (forward-char -1))) |
336 | 434 |
345 (end-of-line)) | 443 (end-of-line)) |
346 (while (and (not (bobp)) (< arg 0)) | 444 (while (and (not (bobp)) (< arg 0)) |
347 (while (and (not (bobp)) | 445 (while (and (not (bobp)) |
348 (re-search-backward (concat "^\\(" outline-regexp "\\)") | 446 (re-search-backward (concat "^\\(" outline-regexp "\\)") |
349 nil 'move) | 447 nil 'move) |
350 (not (outline-visible)))) | 448 (outline-invisible-p))) |
351 (setq arg (1+ arg))) | 449 (setq arg (1+ arg))) |
352 (while (and (not (eobp)) (> arg 0)) | 450 (while (and (not (eobp)) (> arg 0)) |
353 (while (and (not (eobp)) | 451 (while (and (not (eobp)) |
354 (re-search-forward (concat "^\\(" outline-regexp "\\)") | 452 (re-search-forward (concat "^\\(" outline-regexp "\\)") |
355 nil 'move) | 453 nil 'move) |
356 (not (outline-visible)))) | 454 (outline-invisible-p))) |
357 (setq arg (1- arg))) | 455 (setq arg (1- arg))) |
358 (beginning-of-line)) | 456 (beginning-of-line)) |
359 | 457 |
360 (defun outline-previous-visible-heading (arg) | 458 (defun outline-previous-visible-heading (arg) |
361 "Move to the previous heading line. | 459 "Move to the previous heading line. |
378 (setq beg (point)) | 476 (setq beg (point)) |
379 (outline-end-of-subtree) | 477 (outline-end-of-subtree) |
380 (push-mark (point)) | 478 (push-mark (point)) |
381 (goto-char beg))) | 479 (goto-char beg))) |
382 | 480 |
481 | |
482 (put 'outline 'reveal-toggle-invisible 'outline-reveal-toggle-invisible) | |
383 (defun outline-flag-region (from to flag) | 483 (defun outline-flag-region (from to flag) |
384 "Hides or shows lines from FROM to TO, according to FLAG. | 484 "Hide or show lines from FROM to TO, according to FLAG. |
385 If FLAG is nil then text is shown, while if FLAG is t the text is hidden." | 485 If FLAG is nil then text is shown, while if FLAG is t the text is hidden." |
486 (remove-overlays from to 'invisible 'outline) | |
487 (when flag | |
488 (let ((o (make-overlay from to))) | |
489 (overlay-put o 'invisible 'outline) | |
490 (overlay-put o 'isearch-open-invisible 'outline-isearch-open-invisible))) | |
491 ;; Seems only used by lazy-lock. I.e. obsolete. | |
492 (run-hooks 'outline-view-change-hook)) | |
493 | |
494 (defun outline-reveal-toggle-invisible (o revealp) | |
386 (save-excursion | 495 (save-excursion |
387 (goto-char from) | 496 (goto-char (overlay-start o)) |
388 (end-of-line) | 497 (if (null revealp) |
389 (outline-discard-overlays (point) to 'outline) | 498 ;; When hiding the area again, we could just clean it up and let |
390 (if flag | 499 ;; reveal do the rest, by simply doing: |
391 (let ((o (make-overlay (point) to))) | 500 ;; (remove-overlays (overlay-start o) (overlay-end o) |
392 (overlay-put o 'invisible 'outline) | 501 ;; 'invisible 'outline) |
393 (overlay-put o 'isearch-open-invisible | 502 ;; |
394 'outline-isearch-open-invisible)))) | 503 ;; That works fine as long as everything is in sync, but if the |
395 (run-hooks 'outline-view-change-hook)) | 504 ;; structure of the document is changed while revealing parts of it, |
396 | 505 ;; the resulting behavior can be ugly. I.e. we need to make |
506 ;; sure that we hide exactly a subtree. | |
507 (progn | |
508 (let ((end (overlay-end o))) | |
509 (delete-overlay o) | |
510 (while (progn | |
511 (hide-subtree) | |
512 (outline-next-visible-heading 1) | |
513 (and (not (eobp)) (< (point) end)))))) | |
514 | |
515 ;; When revealing, we just need to reveal sublevels. If point is | |
516 ;; inside one of the sublevels, reveal will call us again. | |
517 ;; But we need to preserve the original overlay. | |
518 (let ((o1 (copy-overlay o))) | |
519 (overlay-put o1 'invisible 'outline) ;We rehide some of the text. | |
520 (while (progn | |
521 (show-entry) | |
522 (show-children) | |
523 ;; Normally just the above is needed. | |
524 ;; But in odd cases, the above might fail to show anything. | |
525 ;; To avoid an infinite loop, we have to make sure that | |
526 ;; *something* gets shown. | |
527 (and (equal (overlay-start o) (overlay-start o1)) | |
528 (< (point) (overlay-end o)) | |
529 (= 0 (forward-line 1))))) | |
530 ;; If still nothing was shown, just kill the damn thing. | |
531 (when (equal (overlay-start o) (overlay-start o1)) | |
532 ;; I've seen it happen at the end of buffer. | |
533 (delete-overlay o1)))))) | |
397 | 534 |
398 ;; Function to be set as an outline-isearch-open-invisible' property | 535 ;; Function to be set as an outline-isearch-open-invisible' property |
399 ;; to the overlay that makes the outline invisible (see | 536 ;; to the overlay that makes the outline invisible (see |
400 ;; `outline-flag-region'). | 537 ;; `outline-flag-region'). |
401 (defun outline-isearch-open-invisible (overlay) | 538 (defun outline-isearch-open-invisible (overlay) |
402 ;; We rely on the fact that isearch places point one the matched text. | 539 ;; We rely on the fact that isearch places point on the matched text. |
403 (show-entry)) | 540 (show-entry)) |
404 | |
405 | |
406 ;; Exclude from the region BEG ... END all overlays | |
407 ;; which have PROP as the value of the `invisible' property. | |
408 ;; Exclude them by shrinking them to exclude BEG ... END, | |
409 ;; or even by splitting them if necessary. | |
410 ;; Overlays without such an `invisible' property are not touched. | |
411 (defun outline-discard-overlays (beg end prop) | |
412 (if (< end beg) | |
413 (setq beg (prog1 end (setq end beg)))) | |
414 (save-excursion | |
415 (dolist (o (overlays-in beg end)) | |
416 (if (eq (overlay-get o 'invisible) prop) | |
417 ;; Either push this overlay outside beg...end | |
418 ;; or split it to exclude beg...end | |
419 ;; or delete it entirely (if it is contained in beg...end). | |
420 (if (< (overlay-start o) beg) | |
421 (if (> (overlay-end o) end) | |
422 (progn | |
423 (move-overlay (outline-copy-overlay o) | |
424 (overlay-start o) beg) | |
425 (move-overlay o end (overlay-end o))) | |
426 (move-overlay o (overlay-start o) beg)) | |
427 (if (> (overlay-end o) end) | |
428 (move-overlay o end (overlay-end o)) | |
429 (delete-overlay o))))))) | |
430 | |
431 ;; Make a copy of overlay O, with the same beginning, end and properties. | |
432 (defun outline-copy-overlay (o) | |
433 (let ((o1 (make-overlay (overlay-start o) (overlay-end o) | |
434 (overlay-buffer o))) | |
435 (props (overlay-properties o))) | |
436 (while props | |
437 (overlay-put o1 (car props) (nth 1 props)) | |
438 (setq props (cdr (cdr props)))) | |
439 o1)) | |
440 | 541 |
441 (defun hide-entry () | 542 (defun hide-entry () |
442 "Hide the body directly following this heading." | 543 "Hide the body directly following this heading." |
443 (interactive) | 544 (interactive) |
444 (outline-back-to-heading) | 545 (outline-back-to-heading) |
445 (outline-end-of-heading) | 546 (outline-end-of-heading) |
446 (save-excursion | 547 (save-excursion |
447 (outline-flag-region (point) (progn (outline-next-preface) (point)) t))) | 548 (outline-flag-region (point) (progn (outline-next-preface) (point)) t))) |
448 | 549 |
449 (defun show-entry () | 550 (defun show-entry () |
450 "Show the body directly following this heading. | 551 "Show the body directly following this heading. |
451 Show the heading too, if it is currently invisible." | 552 Show the heading too, if it is currently invisible." |
452 (interactive) | 553 (interactive) |
515 ;; Keep advancing to the next top-level heading. | 616 ;; Keep advancing to the next top-level heading. |
516 (while (or (and (bobp) (outline-on-heading-p)) | 617 (while (or (and (bobp) (outline-on-heading-p)) |
517 (outline-next-heading)) | 618 (outline-next-heading)) |
518 (let ((end (save-excursion (outline-end-of-subtree) (point)))) | 619 (let ((end (save-excursion (outline-end-of-subtree) (point)))) |
519 ;; Hide everything under that. | 620 ;; Hide everything under that. |
621 (outline-end-of-heading) | |
520 (outline-flag-region (point) end t) | 622 (outline-flag-region (point) end t) |
521 ;; Show the first LEVELS levels under that. | 623 ;; Show the first LEVELS levels under that. |
522 (if (> levels 0) | 624 (if (> levels 0) |
523 (show-children levels)) | 625 (show-children levels)) |
524 ;; Move to the next, since we already found it. | 626 ;; Move to the next, since we already found it. |
537 (error nil)) | 639 (error nil)) |
538 (outline-flag-region (1- (point)) | 640 (outline-flag-region (1- (point)) |
539 (save-excursion (forward-line 1) (point)) | 641 (save-excursion (forward-line 1) (point)) |
540 nil)))) | 642 nil)))) |
541 (run-hooks 'outline-view-change-hook)) | 643 (run-hooks 'outline-view-change-hook)) |
644 | |
645 (defun outline-toggle-children () | |
646 "Show or hide the current subtree depending on its current state." | |
647 (interactive) | |
648 (outline-back-to-heading) | |
649 (if (save-excursion | |
650 (end-of-line) | |
651 (not (outline-invisible-p))) | |
652 (hide-subtree) | |
653 (show-children) | |
654 (show-entry))) | |
542 | 655 |
543 (defun outline-flag-subtree (flag) | 656 (defun outline-flag-subtree (flag) |
544 (save-excursion | 657 (save-excursion |
545 (outline-back-to-heading) | 658 (outline-back-to-heading) |
546 (outline-end-of-heading) | 659 (outline-end-of-heading) |
605 (forward-char -1)) | 718 (forward-char -1)) |
606 (point)) | 719 (point)) |
607 (progn (outline-end-of-heading) (point)) | 720 (progn (outline-end-of-heading) (point)) |
608 nil))))))) | 721 nil))))))) |
609 (run-hooks 'outline-view-change-hook)) | 722 (run-hooks 'outline-view-change-hook)) |
723 | |
610 | 724 |
611 (defun outline-up-heading-all (arg) | 725 |
612 "Move to the heading line of which the present line is a subheading. | 726 (defun outline-up-heading (arg &optional invisible-ok) |
613 This function considers both visible and invisible heading lines. | 727 "Move to the visible heading line of which the present line is a subheading. |
614 With argument, move up ARG levels." | 728 With argument, move up ARG levels. |
615 (outline-back-to-heading t) | 729 If INVISIBLE-OK is non-nil, also consider invisible lines." |
730 (interactive "p") | |
731 (outline-back-to-heading invisible-ok) | |
616 (if (eq (funcall outline-level) 1) | 732 (if (eq (funcall outline-level) 1) |
617 (error "Already at top level of the outline")) | 733 (error "Already at top level of the outline")) |
618 (while (and (> (funcall outline-level) 1) | 734 (while (and (> (funcall outline-level) 1) |
619 (> arg 0) | 735 (> arg 0) |
620 (not (bobp))) | 736 (not (bobp))) |
621 (let ((present-level (funcall outline-level))) | 737 (let ((present-level (funcall outline-level))) |
622 (while (and (not (< (funcall outline-level) present-level)) | 738 (while (and (not (< (funcall outline-level) present-level)) |
623 (not (bobp))) | 739 (not (bobp))) |
624 (outline-previous-heading)) | 740 (if invisible-ok |
625 (setq arg (- arg 1))))) | 741 (outline-previous-heading) |
626 | 742 (outline-previous-visible-heading 1))) |
627 (defun outline-up-heading (arg) | |
628 "Move to the visible heading line of which the present line is a subheading. | |
629 With argument, move up ARG levels." | |
630 (interactive "p") | |
631 (outline-back-to-heading) | |
632 (if (eq (funcall outline-level) 1) | |
633 (error "Already at top level of the outline")) | |
634 (while (and (> (funcall outline-level) 1) | |
635 (> arg 0) | |
636 (not (bobp))) | |
637 (let ((present-level (funcall outline-level))) | |
638 (while (and (not (< (funcall outline-level) present-level)) | |
639 (not (bobp))) | |
640 (outline-previous-visible-heading 1)) | |
641 (setq arg (- arg 1))))) | 743 (setq arg (- arg 1))))) |
642 | 744 |
643 (defun outline-forward-same-level (arg) | 745 (defun outline-forward-same-level (arg) |
644 "Move forward to the ARG'th subheading at same level as this one. | 746 "Move forward to the ARG'th subheading at same level as this one. |
645 Stop at the first and last subheadings of a superior heading." | 747 Stop at the first and last subheadings of a superior heading." |
718 (insert-buffer-substring buffer start end) | 820 (insert-buffer-substring buffer start end) |
719 (insert "\n\n"))) | 821 (insert "\n\n"))) |
720 (let ((temp-buffer (current-buffer))) | 822 (let ((temp-buffer (current-buffer))) |
721 (with-current-buffer buffer | 823 (with-current-buffer buffer |
722 (while (outline-next-heading) | 824 (while (outline-next-heading) |
723 (when (outline-visible) | 825 (unless (outline-invisible-p) |
724 (setq start (point) | 826 (setq start (point) |
725 end (progn (outline-end-of-heading) (point))) | 827 end (progn (outline-end-of-heading) (point))) |
726 (with-current-buffer temp-buffer | 828 (with-current-buffer temp-buffer |
727 (insert-buffer-substring buffer start end) | 829 (insert-buffer-substring buffer start end) |
728 (insert "\n\n")))))) | 830 (insert "\n\n")))))) |