# HG changeset patch # User Richard M. Stallman # Date 739043611 0 # Node ID 400db0ca934faa74d5ea741719d6df17a11bf9d6 # Parent 5c0a40a8a55d129d2c230ee9f0604a240143dc32 entered into RCS diff -r 5c0a40a8a55d -r 400db0ca934f lisp/allout.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/allout.el Wed Jun 02 17:53:31 1993 +0000 @@ -0,0 +1,2614 @@ +;;;_* Allout - An extensive outline-mode for Emacs. +;;; Note - the lines beginning with ';;;_' are outline topic headers. +;;; Load this file (or 'eval-current-buffer') and revisit the +;;; file to give it a whirl. + +;;;_ + Provide +(provide 'outline) + +;;;_ + Package Identification Stuff + +;;;_ - Author: Ken Manheimer +;;;_ - Maintainer: Ken Manheimer +;;;_ - Created: Dec 1991 - first release to usenet +;;;_ - Version: $Id: allout.el,v 3.6 1993/06/01 21:30:47 klm Exp $|| +;;;_ - Keywords: outline mode + +;;;_ - LCD Archive Entry + +;; LCD Archive Entry: +;; allout|Ken Manheimer|klm@nist.gov +;; |A more thorough outline-mode +;; |27-May-1993|$Id: allout.el,v 3.4 1993/05/27 19:24:19 klm Exp $|| + +;;;_ - Description +;; A full-fledged outline mode, based on the original rudimentary +;; GNU emacs outline functionality. +;; +;; Ken Manheimer Nat'l Inst of Standards and Technology +;; klm@nist.gov (301)975-3539 (Formerly Nat'l Bureau of Standards) +;; NIST Shared File Service Manager and Developer + +;;;_ - Copyright +;; Copyright (C) 1992, 1993 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY. No author or distributor +;; accepts responsibility to anyone for the consequences of using it +;; or for whether it serves any particular purpose or works at all, +;; unless he says so in writing. Refer to the GNU Emacs General Public +;; License for full details. + +;; Everyone is granted permission to copy, modify and redistribute +;; GNU Emacs, but only under the conditions described in the +;; GNU Emacs General Public License. A copy of this license is +;; supposed to have been given to you along with GNU Emacs so you +;; can know your rights and responsibilities. It should be in a +;; file named COPYING. Among other things, the copyright notice +;; and this notice must be preserved on all copies. + +;;;_ + User Customization variables + +;;;_ - Topic Header configuration + +;;;_ = outline-header-prefix +(defvar outline-header-prefix "." + "* Leading string for greater than level 0 topic headers.") +(make-variable-buffer-local 'outline-header-prefix) + +;;;_ = outline-header-subtraction +(defvar outline-header-subtraction (1- (length outline-header-prefix)) + "* Leading string for greater than level 0 topic headers.") +(make-variable-buffer-local 'outline-header-subtraction) + +;;;_ = outline-primary-bullet +(defvar outline-primary-bullet "*") ;; Changing this var disables any + ;; backwards compatability with + ;; the original outline mode. +(make-variable-buffer-local 'outline-primary-bullet) + +;;;_ = outline-plain-bullets-string +(defvar outline-plain-bullets-string "" + "* The bullets normally used in outline topic prefixes. See + 'outline-distinctive-bullets-string' for the other kind of + bullets. + + DO NOT include the close-square-bracket, ']', among any bullets. + + You must run 'set-outline-regexp' in order for changes to the + value of this var to effect outline-mode operation.") +(setq outline-plain-bullets-string (concat outline-primary-bullet + "+-:.;,")) +(make-variable-buffer-local 'outline-plain-bullets-string) + +;;;_ = outline-distinctive-bullets-string +(defvar outline-distinctive-bullets-string "" + "* The bullets used for distinguishing outline topics. These + bullets are not offered among the regular rotation, and are not + changed when automatically rebulleting, as when shifting the + level of a topic. See 'outline-plain-bullets-string' for the + other kind of bullets. + + DO NOT include the close-square-bracket, ']', among any bullets. + + You must run 'set-outline-regexp' in order for changes + to the value of this var to effect outline-mode operation.") +(setq outline-distinctive-bullets-string "=>([{}&!?#%\"X@$~") +(make-variable-buffer-local 'outline-distinctive-bullets-string) + +;;;_ > outline-numbered-bullet () +(defvar outline-numbered-bullet () + "* Bullet signifying outline prefixes which are to be numbered. + Leave it nil if you don't want any numbering, or set it to a + string with the bullet you want to be used.") +(setq outline-numbered-bullet "#") +(make-variable-buffer-local 'outline-numbered-bullet) + +;;;_ = outline-file-xref-bullet +(defvar outline-file-xref-bullet "@" + "* Set this var to the bullet you want to use for file cross-references. + Set it 'nil' if you want to inhibit this capability.") + +;;;_ - Miscellaneous customization + +;;;_ = outline-stylish-prefixes +(defvar outline-stylish-prefixes t + "*A true value for this var makes the topic-prefix creation and modification + functions vary the prefix bullet char according to level. Otherwise, only + asterisks ('*') and distinctive bullets are used. + + This is how an outline can look with stylish prefixes: + + * Top level + .* A topic + . + One level 3 subtopic + . . One level 4 subtopic + . + Another level 3 subtopic + . . A level 4 subtopic + . #2 A distinguished, numbered level 4 subtopic + . ! A distinguished ('!') level 4 subtopic + . #4 Another numbered level 4 subtopic + + This would be an outline with stylish prefixes inhibited: + + * Top level + .* A topic + .! A distinctive (but measly) subtopic + . * A sub-subtopic - no bullets from outline-plain-bullets-string but '*' + + Stylish and constant prefixes (as well as old-style prefixes) are + always respected by the topic maneuvering functions, regardless of + this variable setting. + + The setting of this var is not relevant when outline-old-style-prefixes + is t.") +(make-variable-buffer-local 'outline-stylish-prefixes) + +;;;_ = outline-old-style-prefixes +(defvar outline-old-style-prefixes nil + "*Setting this var causes the topic-prefix creation and modification + functions to make only asterix-padded prefixes, so they look exactly + like the old style prefixes. + + Both old and new style prefixes are always respected by the topic + maneuvering functions.") +(make-variable-buffer-local 'outline-old-style-prefixes) + +;;;_ = outline-enwrap-isearch-mode + ; Spiffy dynamic-exposure + ; during searches requires + ; Dan LaLiberte's isearch-mode: +(defvar outline-enwrap-isearch-mode "isearch-mode.el" + "* Set this var to the name of the (non-compiled) elisp code for + isearch-mode, if you have Dan LaLiberte's 'isearch-mode' + stuff and want isearches to reveal hidden stuff encountered in the + course of a search, and reconceal it if you go past. Set it nil if + you don't have the package, or don't want to use this feature.") + +;;;_ = outline-use-hanging-indents +(defvar outline-use-hanging-indents t + "* Set this var non-nil if you have Kyle E Jones' filladapt stuff, + and you want outline to fill topics as hanging indents to the + bullets.") +(make-variable-buffer-local 'outline-use-hanging-indents) + +;;;_ = outline-reindent-bodies +(defvar outline-reindent-bodies t + "* Set this var non-nil if you want topic depth adjustments to + reindent hanging bodies (ie, bodies lines indented to beginning of + heading text). The performance hit is small. + + Avoid this strenuously when using outline mode on program code. + It's great for text, though.") +(make-variable-buffer-local 'outline-reindent-bodies) + +;;;_ = outline-mode-keys +;;; You have to restart outline-mode - '(outline-mode t)' - to have +;;; any changes take hold. +(defvar outline-mode-keys () + "Assoc list of outline-mode-keybindings, for common reference in setting +up major and minor-mode keybindings.") +(setq outline-mode-keys + '( + ; Motion commands: + ("\C-c\C-n" outline-next-visible-heading) + ("\C-c\C-p" outline-previous-visible-heading) + ("\C-c\C-u" outline-up-current-level) + ("\C-c\C-f" outline-forward-current-level) + ("\C-c\C-b" outline-backward-current-level) + ("\C-c\C-a" outline-beginning-of-current-entry) + ("\C-c\C-e" outline-end-of-current-entry) + ; Exposure commands: + ("\C-c\C-i" outline-show-current-children) + ("\C-c\C-s" outline-show-current-subtree) + ("\C-c\C-h" outline-hide-current-subtree) + ("\C-c\C-o" outline-show-current-entry) + ("\C-c!" outline-show-all) + ; Alteration commands: + ("\C-c " open-sibtopic) + ("\C-c." open-subtopic) + ("\C-c," open-supertopic) + ("\C-c'" outline-shift-in) + ("\C-c>" outline-shift-in) + ("\C-c<" outline-shift-out) + ("\C-c\C-m" outline-rebullet-topic) + ("\C-cb" outline-rebullet-current-heading) + ("\C-c#" outline-number-siblings) + ("\C-k" outline-kill-line) + ("\C-y" outline-yank) + ("\M-y" outline-yank-pop) + ("\C-c\C-k" outline-kill-topic) + ; Miscellaneous commands: + ("\C-c@" outline-resolve-xref) + ("\C-cc" outline-copy-exposed))) + +;;;_ + Code - no user customizations below. + +;;;_ #1 Outline Format and Internal Mode Configuration + +;;;_ : Topic header format +;;;_ = outline-regexp +(defvar outline-regexp "" + "* Regular expression to match the beginning of a heading line. + Any line whose beginning matches this regexp is considered a + heading. This var is set according to the user configuration vars + by set-outline-regexp.") +(make-variable-buffer-local 'outline-regexp) +;;;_ = outline-bullets-string +(defvar outline-bullets-string "" + " A string dictating the valid set of outline topic bullets. This + var should *not* be set by the user - it is set by 'set-outline-regexp', + and is composed from the elements of 'outline-plain-bullets-string' + and 'outline-distinctive-bullets-string'.") +(make-variable-buffer-local 'outline-bullets-string) +;;;_ = outline-line-boundary-regexp +(defvar outline-line-boundary-regexp () + " outline-regexp with outline-style beginning of line anchor (ie, + C-j, *or* C-m, for prefixes of hidden topics). This is properly + set when outline-regexp is produced by 'set-outline-regexp', so + that (match-beginning 2) and (match-end 2) delimit the prefix.") +(make-variable-buffer-local 'outline-line-boundary-regexp) +;;;_ = outline-bob-regexp +(defvar outline-bob-regexp () + " Like outline-line-boundary-regexp, this is an outline-regexp for + outline headers at the beginning of the buffer. (match-beginning 2) + and (match-end 2) + delimit the prefix.") +(make-variable-buffer-local 'outline-line-bob-regexp) +;;;_ > outline-reset-header-lead (header-lead) +(defun outline-reset-header-lead (header-lead) + "* Reset the leading string used to identify topic headers." + (interactive "sNew lead string: ") + ;;() + (setq outline-header-prefix header-lead) + (setq outline-header-subtraction (1- (length outline-header-prefix))) + (set-outline-regexp) + ) +;;;_ > outline-lead-with-comment-string (header-lead) +(defun outline-lead-with-comment-string (&optional header-lead) + "* Set the topic-header leading string to specified string. Useful + when for encapsulating outline structure in programming language + comments. Returns the leading string." + + (interactive "P") + (if (not (stringp header-lead)) + (setq header-lead (read-string + "String prefix for topic headers: "))) + (setq outline-reindent-bodies nil) + (outline-reset-header-lead header-lead) + header-lead) +;;;_ > set-outline-regexp () +(defun set-outline-regexp () + " Generate proper topic-header regexp form for outline functions, from + outline-plain-bullets-string and outline-distinctive-bullets-string." + + (interactive) + ;; Derive outline-bullets-string from user configured components: + (setq outline-bullets-string "") + (let ((strings (list 'outline-plain-bullets-string + 'outline-distinctive-bullets-string)) + cur-string + cur-len + cur-char-string + index + new-string) + (while strings + (setq new-string "") (setq index 0) + (setq cur-len (length (setq cur-string (symbol-value (car strings))))) + (while (< index cur-len) + (setq cur-char (aref cur-string index)) + (setq outline-bullets-string + (concat outline-bullets-string + (cond + ; Single dash would denote a + ; sequence, repeated denotes + ; a dash: + ((eq cur-char ?-) "--") + ; literal close-square-bracket + ; doesn't work right in the + ; expr, exclude it: + ((eq cur-char ?\]) "") + (t (regexp-quote (char-to-string cur-char)))))) + (setq index (1+ index))) + (setq strings (cdr strings))) + ) + ;; Derive next for repeated use in outline-pending-bullet: + (setq outline-plain-bullets-string-len (length outline-plain-bullets-string)) + (setq outline-header-subtraction (1- (length outline-header-prefix))) + ;; Produce the new outline-regexp: + (setq outline-regexp (concat "\\(\\" + outline-header-prefix + "[ \t]*[" + outline-bullets-string + "]\\)\\|\\" + outline-primary-bullet + "+\\|\^l")) + (setq outline-line-boundary-regexp + (concat "\\([\C-j\C-m]\\)\\(" outline-regexp "\\)")) + (setq outline-bob-regexp + (concat "\\(\\`\\)\\(" outline-regexp "\\)")) + ) + +;;;_ : Key bindings +;;;_ = Generic minor keybindings control +;;;_ ; Stallmans suggestion +(defvar outline-mode-map nil "") + +(if outline-mode-map + nil + (setq outline-mode-map (nconc (make-sparse-keymap) text-mode-map)) + (define-key outline-mode-map "\C-c\C-n" 'outline-next-visible-heading) + (define-key outline-mode-map "\C-c\C-p" 'outline-previous-visible-heading) + (define-key outline-mode-map "\C-c\C-i" 'show-children) + (define-key outline-mode-map "\C-c\C-s" 'show-subtree) + (define-key outline-mode-map "\C-c\C-h" 'hide-subtree) + (define-key outline-mode-map "\C-c\C-u" 'outline-up-heading) + (define-key outline-mode-map "\C-c\C-f" 'outline-forward-same-level) + (define-key outline-mode-map "\C-c\C-b" 'outline-backward-same-level)) + +(defvar outline-minor-mode nil + "Non-nil if using Outline mode as a minor mode of some other mode.") +(make-variable-buffer-local 'outline-minor-mode) +(put 'outline-minor-mode 'permanent-local t) +(setq minor-mode-alist (append minor-mode-alist + (list '(outline-minor-mode " Outl")))) + +(defvar outline-minor-mode-map nil) +(if outline-minor-mode-map + nil + (setq outline-minor-mode-map (make-sparse-keymap)) + (define-key outline-minor-mode-map "\C-c" + (lookup-key outline-mode-map "\C-c"))) + +(or (assq 'outline-minor-mode minor-mode-map-alist) + (setq minor-mode-map-alist + (cons (cons 'outline-minor-mode outline-minor-mode-map) + minor-mode-map-alist))) + +(defun outline-minor-mode (&optional arg) + "Toggle Outline minor mode. +With arg, turn Outline minor mode on if arg is positive, off otherwise. +See the command `outline-mode' for more information on this mode." + (interactive "P") + (setq outline-minor-mode + (if (null arg) (not outline-minor-mode) + (> (prefix-numeric-value arg) 0))) + (if outline-minor-mode + (progn + (setq selective-display t) + (run-hooks 'outline-minor-mode-hook)) + (setq selective-display nil))) +;;;_ ; minor-bind-keys (keys-assoc) +(defun minor-bind-keys (keys-assoc) + " Establish BINDINGS assoc list in current buffer, returning a list + for subsequent use by minor-unbind-keys to resume overloaded local + bindings." + (interactive) + ;; Cycle thru key list, registering prevailing local binding for key, if + ;; any (for prospective resumption by outline-minor-unbind-keys), then + ;; overloading it with outline-mode one. + (let ((local-map (or (current-local-map) + (make-sparse-keymap))) + key new-func unbinding-registry prevailing-func) + (while keys-assoc + (setq curr-key (car (car keys-assoc))) + (setq new-func (car (cdr (car keys-assoc)))) + (setq prevailing-func (local-key-binding curr-key)) + (if (not (symbolp prevailing-func)) + (setq prevailing-func nil)) + ;; Register key being changed, prevailing local binding, & new binding: + (setq unbinding-registry + (cons (list curr-key (local-key-binding curr-key) new-func) + unbinding-registry)) + ; Make the binding: + + (define-key local-map curr-key new-func) + ; Increment for next iteration: + (setq keys-assoc (cdr keys-assoc))) + ; Establish modified map: + (use-local-map local-map) + ; Return the registry: + unbinding-registry) + ) + +;;;_ ; minor-relinquish-keys (unbinding-registry) +(defun minor-relinquish-keys (unbinding-registry) + " Given registry of MODAL-BINDINGS, as produced by minor-bind-keys, + resume the former local keybindings of those keys that retain the + local bindings set by minor-bind-keys. Changed local bindings are + left alone, so other minor (user or modal) bindings are not disrupted. + + Returns a list of those registrations which were not, because of + tampering subsequent to the registration by minor-bind-keys, resumed." + (interactive) + (let (residue curr-item curr-key curr-resume curr-relinquish) + (while unbinding-registry + (setq curr-item (car unbinding-registry)) + (setq curr-key (car curr-item)) + (setq curr-resume (car (cdr curr-item))) + (setq curr-relinquish (car (cdr (cdr curr-item)))) + (if (equal (local-key-binding curr-key) curr-relinquish) + (if curr-resume + ;; Was a local binding to be resumed - do so: + (local-set-key curr-key curr-resume) + (local-unset-key curr-key)) + ;; Bindings been tampered with since registration - leave it be, and + ;; register so on residue list: + (setq residue (cons residue curr-item))) + (setq unbinding-registry (cdr unbinding-registry))) + residue) + ) +;;;_ = outline-minor-prior-keys +(defvar outline-minor-prior-keys () + "Former key bindings assoc-list, for resumption from outline minor-mode.") +(make-variable-buffer-local 'outline-minor-prior-keys) + + ; Both major and minor mode + ; bindings are dictated by + ; this list - put your + ; settings here. +;;;_ > outline-minor-bind-keys () +(defun outline-minor-bind-keys () + " Establish outline-mode keybindings as MINOR modality of current buffer." + (setq outline-minor-prior-keys + (minor-bind-keys outline-mode-keys))) +;;;_ > outline-minor-relinquish-keys () +(defun outline-minor-relinquish-keys () + " Resurrect local keybindings as they were before outline-minor-bind-keys." + (minor-relinquish-keys outline-minor-prior-keys) +) + +;;;_ : Mode-Specific Variables Maintenance +;;;_ = outline-mode-prior-settings +(defvar outline-mode-prior-settings nil + "For internal use by outline mode, registers settings to be resumed +on mode deactivation.") +(make-variable-buffer-local 'outline-mode-prior-settings) +;;;_ > outline-resumptions (name &optional value) +(defun outline-resumptions (name &optional value) + + " Registers information for later reference, or performs resumption of + outline-mode specific values. First arg is NAME of variable affected. + optional second arg is list containing outline-mode-specific VALUE to + be impose on named variable, and to be registered. (It's a list so you + can specify registrations of null values.) If no value is specified, + the registered value is returned (encapsulated in the list, so the + caller can distinguish nil vs no value), and the registration is popped + from the list." + + (let ((on-list (assq name outline-mode-prior-settings)) + prior-capsule ; By 'capsule' i mean a list + ; containing a value, so we can + ; distinguish nil from no value. + ) + + (if value + + ;; Registering: + (progn + (if on-list + nil ; Already preserved prior value - don't mess with it. + ;; Register the old value, or nil if previously unbound: + (setq outline-mode-prior-settings + (cons (list name + (if (boundp name) (list (symbol-value name)))) + outline-mode-prior-settings))) + ; And impose the new value: + (set name (car value))) + + ;; Relinquishing: + (if (not on-list) + + ;; Oops, not registered - leave it be: + nil + + ;; Some registration: + ; reestablish it: + (setq prior-capsule (car (cdr on-list))) + (if prior-capsule + (set name (car prior-capsule)) ; Some prior value - reestablish it. + (makunbound name)) ; Previously unbound - demolish var. + ; Remove registration: + (let (rebuild) + (while outline-mode-prior-settings + (if (not (eq (car outline-mode-prior-settings) + on-list)) + (setq rebuild + (cons (car outline-mode-prior-settings) + rebuild))) + (setq outline-mode-prior-settings + (cdr outline-mode-prior-settings))) + (setq outline-mode-prior-settings rebuild))))) + ) + +;;;_ : Overall +;;;_ = outline-mode +(defvar outline-mode () "Allout outline mode minor-mode flag.") +(make-variable-buffer-local 'outline-mode) +;;;_ > outline-mode (&optional toggle) +(defun outline-mode (&optional toggle) + " Set minor mode for editing outlines with selective display. + + Look below the description of the bindings for explanation of the + terminology use in outline-mode commands. + + (Note - this is not a proper minor mode, because it does affect key + bindings. It's not too improper, however, because it does resurrect + any bindings which have not been tampered with since it changed them.) + +Exposure Commands Movement Commands +C-c C-h outline-hide-current-subtree C-c C-n outline-next-visible-heading +C-c C-i outline-show-current-children C-c C-p outline-previous-visible-heading +C-c C-s outline-show-current-subtree C-c C-u outline-up-current-level +C-c C-o outline-show-current-entry C-c C-f outline-forward-current-level +C-c ! outline-show-all C-c C-b outline-backward-current-level + outline-hide-current-leaves C-c C-e outline-end-of-current-entry + C-c C-a outline-beginning-of-current-entry + + +Topic Header Generation Commands +C-c open-sibtopic Create a new sibling after current topic +C-c . open-subtopic ... an offspring of current topic +C-c , open-supertopic ... a sibling of the current topic's parent + +Level and Prefix Adjustment Commands +C-c > outline-shift-in Shift current topic and all offspring deeper +C-c < outline-shift-out ... less deep +C-c outline-rebullet-topic Reconcile bullets of topic and its offspring + - distinctive bullets are not changed, all + others set suitable according to depth +C-c b outline-rebullet-current-heading Prompt for alternate bullet for + current topic +C-c # outline-number-siblings Number bullets of topic and siblings - the + offspring are not affected. With repeat + count, revoke numbering. + +Killing and Yanking - all keep siblings numbering reconciled as appropriate +C-k outline-kill-line Regular kill line, but respects numbering ,etc +C-c C-k outline-kill-topic Kill current topic, including offspring +C-y outline-yank Yank, adjusting depth of yanked topic to + depth of heading if yanking into bare topic + heading (ie, prefix sans text) +M-y outline-yank-pop Is to outline-yank as yank-pop is to yank + +Misc commands +C-c @ outline-resolve-xref pop-to-buffer named by xref (cf + outline-file-xref-bullet) +C-c c outline-copy-exposed Copy outline sans all hidden stuff to + another buffer whose name is derived + from the current one - \"XXX exposed\" +M-x outlineify-sticky Activate outline mode for current buffer + and establish -*- outline -*- mode specifier + as well as file local vars to automatically + set exposure. Try it. + + Terminology + +Topic: A basic cohesive component of an emacs outline, which can + be closed (made hidden), opened (revealed), generated, + traversed, and shifted as units, using outline-mode functions. + A topic is composed of a HEADER, a BODY, and SUBTOPICs (see below). + +Exposure: Hidden (~closed~) topics are represented by ellipses ('...') + at the end of the visible SUPERTOPIC which contains them, + rather than by their actual text. Hidden topics are still + susceptable to editing and regular movement functions, they + just are not displayed normally, effectively collapsed into + the ellipses which represent them. Outline mode provides + the means to selectively expose topics based on their + NESTING. + + SUBTOPICS of a topic can be hidden and subsequently revealed + based on their DEPTH relative to the supertopic from which + the exposure is being done. + + The BODIES of a topic do not generally become visible except + during exposure of entire subtrees (see documentation for + '-current-subtree'), or when the entry is explicitly exposed + with the 'outline-show-entry' function, or (if you have a + special version of isearch installed) when encountered by + incremental searches. + + The CURRENT topic is the more recent visible one before or + including the text cursor. + +Header: The initial portion of an outline topic. It is composed of a + topic header PREFIX at the beginning of the line, followed by + text to the end of the EFFECTIVE LINE. + +Body: Any subsequent lines of text following a topic header and preceeding + the next one. This is also referred to as the entry for a topic. + +Prefix: The text which distinguishes topic headers from normal text + lines. There are two forms, both of which start at the beginning + of the topic header (EFFECTIVE) line. The length of the prefix + represents the DEPTH of the topic. The fundamental sort begins + either with solely an asterisk ('*') or else dot ('.') followed + by zero or more spaces and then an outline BULLET. [Note - you + can now designate your own, arbitrary HEADER-LEAD string, by + setting the variable 'outline-header-prefix'.] The second form + is for backwards compatability with the original emacs outline + mode, and consists solely of asterisks. Both sorts are + recognized by all outline commands. The first sort is generated + by outline topic production commands if the emacs variable + outline-old-style-prefixes is nil, otherwise the second style is + used. + +Bullet: An outline prefix bullet is one of the characters on either + of the outline bullet string vars, 'outline-plain-bullets-string' + and 'outline-distinctive-bullets-string'. (See their + documentation for more details.) The default choice of bullet + for any prefix depends on the DEPTH of the topic. + +Depth and Nesting: + The length of a topic header prefix, from the initial + character to the bullet (inclusive), represents the depth of + the topic. A topic is considered to contain the subsequent + topics of greater depth up to the next topic of the same + depth, and the contained topics are recursively considered to + be nested within all containing topics. Contained topics are + called subtopics. Immediate subtopics are called 'children'. + Containing topics are supertopicsimmediate supertopics are + 'parents'. Contained topics of the same depth are called + siblings. + +Effective line: The regular ascii text in which form outlines are + saved are manipulated in outline-mode to engage emacs' + selective-display faculty. The upshot is that the + effective end of an outline line can be terminated by + either a normal Unix newline char, \n, or the special + outline-mode eol, ^M. This only matters at the user + level when you're doing searches which key on the end of + line character." + + (interactive "P") + + (let* ((active (and (boundp 'outline-mode) outline-mode)) + (toggle (and toggle + (or (and (listp toggle)(car toggle)) + toggle))) + (explicit-activation (and toggle + (or (symbolp toggle) + (and (natnump toggle) + (not (zerop toggle))))))) + + (cond + + ((and (not explicit-activation) (or active toggle)) + ;; Activation not explicitly requested, and either in active + ;; state or deactivation specifically requested: + (outline-minor-relinquish-keys) + (outline-resumptions 'selective-display) + (outline-resumptions 'indent-tabs-mode) + (outline-resumptions 'paragraph-start) + (outline-resumptions 'paragraph-separate) + (setq outline-mode nil)) + + ;; Deactivation *not* indicated. + ((not active) + ;; Not already active - activate: + (outline-minor-bind-keys) + (outline-resumptions 'selective-display '(t)) + (outline-resumptions 'indent-tabs-mode '(nil)) + (or (assq 'outline-mode minor-mode-alist) + (setq minor-mode-alist + (cons '(outline-mode " Outline") minor-mode-alist))) + (set-outline-regexp) + + (make-local-variable 'paragraph-start) + (outline-resumptions 'paragraph-start + (list (concat paragraph-start "\\|^\\(" + outline-regexp "\\)"))) + (make-local-variable 'paragraph-separate) + (outline-resumptions 'paragraph-separate + (list (concat paragraph-separate "\\|^\\(" + outline-regexp "\\)"))) + + (if outline-enwrap-isearch-mode + (outline-enwrap-isearch)) + (if (and outline-use-hanging-indents + (boundp 'filladapt-prefix-table)) + ;; Add outline-prefix recognition to filladapt - not standard: + (progn (setq filladapt-prefix-table + (cons (cons (concat "\\(" outline-regexp "\\) ") + 'filladapt-hanging-list) + filladapt-prefix-table)) + (setq filladapt-hanging-list-prefixes + (cons outline-regexp + filladapt-hanging-list-prefixes)))) + (run-hooks 'outline-mode-hook) + (setq outline-mode t)) + ) ; cond + ) ; let* + ) ; defun + + +;;;_ #2 Internal Position State-Tracking Variables +;;; All basic outline functions which directly do string matches to +;;; evaluate heading prefix location set the variables +;;; outline-recent-prefix-beginning and outline-recent-prefix-end when +;;; successful. Functions starting with 'outline-recent-' all use +;;; this state, providing the means to avoid redundant searches for +;;; just established data. This optimization can provide significant +;;; speed improvement, but it must be employed carefully. +;;;_ = outline-recent-prefix-beginning +(defvar outline-recent-prefix-beginning 0 + " Buffer point of the start of the last topic prefix encountered.") +(make-variable-buffer-local 'outline-recent-prefix-beginning) +;;;_ = outline-recent-prefix-end +(defvar outline-recent-prefix-end 0 + " Buffer point of the end of the last topic prefix encountered.") +(make-variable-buffer-local 'outline-recent-prefix-end) + +;;;_ #3 Exposure Control + +;;;_ : Fundamental +;;;_ > outline-flag-region (from to flag) +(defun outline-flag-region (from to flag) + " Hides or shows lines from FROM to TO, according to FLAG. + Uses emacs selective-display, where text is show if FLAG put at + beginning of line is `\\n' (newline character), while text is + hidden if FLAG is `\\^M' (control-M). + + returns nil iff no changes were effected." + (let ((buffer-read-only nil)) + (subst-char-in-region from to + (if (= flag ?\n) ?\^M ?\n) + flag t))) +;;;_ > outline-flag-current-subtree (flag) +(defun outline-flag-current-subtree (flag) + (save-excursion + (outline-back-to-current-heading) + (outline-flag-region (point) + (progn (outline-end-of-current-subtree) (point)) + flag))) + +;;;_ : Topic-specific +;;;_ > outline-hide-current-entry () +(defun outline-hide-current-entry () + "Hide the body directly following this heading." + (interactive) + (outline-back-to-current-heading) + (save-excursion + (outline-flag-region (point) + (progn (outline-end-of-current-entry) (point)) + ?\^M))) +;;;_ > outline-show-current-entry (&optional arg) +(defun outline-show-current-entry (&optional arg) + "Show body directly following this heading, or hide it if repeat count." + (interactive "P") + (if arg + (outline-hide-current-entry) + (save-excursion + (outline-flag-region (point) + (progn (outline-end-of-current-entry) (point)) + ?\n)))) +;;;_ > outline-show-entry () +; outline-show-entry basically for isearch dynamic exposure, as is... +(defun outline-show-entry () + " Like outline-show-current-entry, but reveals an entry that is nested + within hidden topics." + (interactive) + (save-excursion + (outline-goto-prefix) + (outline-flag-region (if (not (bobp)) (1- (point)) (point)) + (progn (outline-pre-next-preface) (point)) ?\n))) +;;;_ > outline-hide-current-entry-completely () +; ... outline-hide-current-entry-completely also for isearch dynamic exposure: +(defun outline-hide-current-entry-completely () + "Like outline-hide-current-entry, but conceal topic completely." + (interactive) + (save-excursion + (outline-goto-prefix) + (outline-flag-region (if (not (bobp)) (1- (point)) (point)) + (progn (outline-pre-next-preface) + (if (looking-at "\C-m") + (point) + (1- (point)))) + ?\C-m))) +;;;_ > outline-show-current-subtree () +(defun outline-show-current-subtree () + "Show everything after this heading at deeper levels." + (interactive) + (outline-flag-current-subtree ?\n)) +;;;_ > outline-hide-current-subtree (&optional just-close) +(defun outline-hide-current-subtree (&optional just-close) + + " Hide everything after this heading at deeper levels, or if it's + already closed, and optional arg JUST-CLOSE is nil, hide the current + level." + + (interactive) + (let ((orig-eol (save-excursion + (end-of-line)(outline-goto-prefix)(end-of-line)(point)))) + (outline-flag-current-subtree ?\^M) + (if (and (= orig-eol (save-excursion (goto-char orig-eol) + (end-of-line) + (point))) + ;; Structure didn't change - try hiding current level: + (if (not just-close) + (outline-up-current-level 1 t))) + (outline-hide-current-subtree)))) +;;;_ > outline-show-current-branches () +(defun outline-show-current-branches () + "Show all subheadings of this heading, but not their bodies." + (interactive) + (outline-show-current-children 1000)) +;;;_ > outline-hide-current-leaves () +(defun outline-hide-current-leaves () + "Hide all body after this heading at deeper levels." + (interactive) + (outline-back-to-current-heading) + (outline-hide-region-body (point) (progn (outline-end-of-current-subtree) + (point)))) +;;;_ > outline-show-current-children (&optional level) +(defun outline-show-current-children (&optional level) + " Show all direct subheadings of this heading. Optional LEVEL specifies + how many levels below the current level should be shown." + (interactive "p") + (or level (setq level 1)) + (save-excursion + (save-restriction + (beginning-of-line) + (setq level (+ level (progn (outline-back-to-current-heading) + (outline-recent-depth)))) + (narrow-to-region (point) + (progn (outline-end-of-current-subtree) (1+ (point)))) + (goto-char (point-min)) + (while (and (not (eobp)) + (outline-next-heading)) + (if (<= (outline-recent-depth) level) + (save-excursion + (let ((end (1+ (point)))) + (forward-char -1) + (if (memq (preceding-char) '(?\n ?\^M)) + (forward-char -1)) + (outline-flag-region (point) end ?\n)))))))) + +;;;_ : Region and beyond +;;;_ > outline-show-all () +(defun outline-show-all () + "Show all of the text in the buffer." + (interactive) + (outline-flag-region (point-min) (point-max) ?\n)) +;;;_ > outline-hide-bodies () +(defun outline-hide-bodies () + "Hide all of buffer except headings." + (interactive) + (outline-hide-region-body (point-min) (point-max))) +;;;_ > outline-hide-region-body (start end) +(defun outline-hide-region-body (start end) + "Hide all body lines in the region, but not headings." + (save-excursion + (save-restriction + (narrow-to-region start end) + (goto-char (point-min)) + (while (not (eobp)) + (outline-flag-region (point) + (progn (outline-pre-next-preface) (point)) ?\^M) + (if (not (eobp)) + (forward-char + (if (looking-at "[\n\^M][\n\^M]") + 2 1))))))) +;;;_ > outline-expose () +(defun outline-expose (spec &rest followers) + + "Dictate wholesale exposure scheme for current topic, according to SPEC. + +SPEC is either a number or a list of specs. Optional successive args +dictate exposure for subsequent siblings of current topic. + +Numbers, the symbols '*' and '+', and the null list dictate different +exposure depths for the corresponding topic. Numbers indicate the +depth to open, with negative numbers first forcing a close, and then +opening to their absolute value. Positive numbers jsut reopen, and 0 +just closes. '*' completely opens the topic, including bodies, and +'+' shows all the sub headers, but not the bodies. + +If the spec is a list, the first element must be a number which +dictates the exposure depth of the topic as a whole. Subsequent +elements of the list are nested SPECs, dictating the specific exposure +for the corresponding offspring of the topic, as the SPEC as a whole +does for the parent topic. + +Optional FOLLOWER elements dictate exposure for subsequent siblings +of the parent topic." + + (interactive "xExposure spec: ") + (save-excursion + (let ((start-point (progn (outline-goto-prefix)(point))) + done) + (cond ((null spec) nil) + ((symbolp spec) + (if (eq spec '*) (outline-show-current-subtree)) + (if (eq spec '+) (outline-show-current-branches))) + ((numberp spec) + (if (zerop spec) + ;; Just hide if zero: + (outline-hide-current-subtree t) + (if (> 0 spec) + ;; Close before opening if negative: + (progn (outline-hide-current-subtree) + (setq spec (* -1 spec)))) + (outline-show-current-children spec))) + ((listp spec) + (outline-expose (car spec)) + (if (and (outline-descend-to-depth (+ (outline-current-depth) 1)) + (not (outline-hidden-p))) + (while (and (setq spec (cdr spec)) + (not done)) + (outline-expose (car spec)) + (setq done (not (outline-next-sibling))))))))) + (while (and followers (outline-next-sibling)) + (outline-expose (car followers)) + (setq followers (cdr followers))) + ) +;;;_ > outline-exposure '() +(defmacro outline-exposure (&rest spec) + " Literal frontend for 'outline-expose', passes arguments unevaluated, + so you needn't quote them." + (cons 'outline-expose (mapcar '(lambda (x) (list 'quote x)) spec))) + +;;;_ #4 Navigation + +;;;_ : Position Assessment + +;;;_ . Residual state - from most recent outline context operation. +;;;_ > outline-recent-depth () +(defun outline-recent-depth () + " Return depth of last heading encountered by an outline maneuvering + function. + + All outline functions which directly do string matches to assess + headings set the variables outline-recent-prefix-beginning and + outline-recent-prefix-end if successful. This function uses those settings + to return the current depth." + + (max 1 + (- outline-recent-prefix-end + outline-recent-prefix-beginning + outline-header-subtraction))) +;;;_ > outline-recent-prefix () +(defun outline-recent-prefix () + " Like outline-recent-depth, but returns text of last encountered prefix. + + All outline functions which directly do string matches to assess + headings set the variables outline-recent-prefix-beginning and + outline-recent-prefix-end if successful. This function uses those settings + to return the current depth." + (buffer-substring outline-recent-prefix-beginning outline-recent-prefix-end)) +;;;_ > outline-recent-bullet () +(defun outline-recent-bullet () + " Like outline-recent-prefix, but returns bullet of last encountered + prefix. + + All outline functions which directly do string matches to assess + headings set the variables outline-recent-prefix-beginning and + outline-recent-prefix-end if successful. This function uses those settings + to return the current depth of the most recently matched topic." + (buffer-substring (1- outline-recent-prefix-end) outline-recent-prefix-end)) + +;;;_ . Active position evaluation - if you can't use the residual state. +;;;_ > outline-on-current-heading-p () +(defun outline-on-current-heading-p () + " Return prefix beginning point if point is on same line as current + visible topic's header line." + (save-excursion + (beginning-of-line) + (and (looking-at outline-regexp) + (setq outline-recent-prefix-end (match-end 0) + outline-recent-prefix-beginning (match-beginning 0))))) +;;;_ > outline-hidden-p () +(defun outline-hidden-p () + "True if point is in hidden text." + (interactive) + (save-excursion + (and (re-search-backward "[\C-j\C-m]" (point-min) t) + (looking-at "\C-m")))) +;;;_ > outline-current-depth () +(defun outline-current-depth () + " Return the depth to which the current containing visible topic is + nested in the outline." + (save-excursion + (if (outline-back-to-current-heading) + (max 1 + (- outline-recent-prefix-end + outline-recent-prefix-beginning + outline-header-subtraction)) + 0))) +;;;_ > outline-depth () +(defun outline-depth () + " Like outline-current-depth, but respects hidden as well as visible + topics." + (save-excursion + (if (outline-goto-prefix) + (outline-recent-depth) + (progn + (setq outline-recent-prefix-end (point) + outline-recent-prefix-beginning (point)) + 0)))) +;;;_ > outline-get-current-prefix () +(defun outline-get-current-prefix () + " Topic prefix of the current topic." + (save-excursion + (if (outline-goto-prefix) + (outline-recent-prefix)))) +;;;_ > outline-get-bullet () +(defun outline-get-bullet () + " Return bullet of containing topic (visible or not)." + (save-excursion + (and (outline-goto-prefix) + (outline-recent-bullet)))) +;;;_ > outline-current-bullet () +(defun outline-current-bullet () + " Return bullet of current (visible) topic heading, or none if none found." + (condition-case err + (save-excursion + (outline-back-to-current-heading) + (buffer-substring (- outline-recent-prefix-end 1) + outline-recent-prefix-end)) + ;; Quick and dirty provision, ostensibly for missing bullet: + (args-out-of-range nil)) + ) +;;;_ > outline-get-prefix-bullet (prefix) +(defun outline-get-prefix-bullet (prefix) + " Return the bullet of the header prefix string PREFIX." + ;; Doesn't make sense if we're old-style prefixes, but this just + ;; oughtn't be called then, so forget about it... + (if (string-match outline-regexp prefix) + (substring prefix (1- (match-end 0)) (match-end 0)))) + +;;;_ : Within Topic +;;;_ > outline-goto-prefix () +(defun outline-goto-prefix () + " Put point at beginning of outline prefix for current topic, visible + or not. + + Returns a list of char address of the beginning of the prefix and the + end of it, or nil if none." + + (cond ((and (or (save-excursion (beginning-of-line) (bobp)) + (memq (preceding-char) '(?\n ?\^M))) + (looking-at outline-regexp)) + (setq outline-recent-prefix-end (match-end 0) + outline-recent-prefix-beginning + (goto-char (match-beginning 0)))) + ((re-search-backward outline-line-boundary-regexp + ;; unbounded search, + ;; stay at limit and return nil if failed: + nil 1) + (setq outline-recent-prefix-end (match-end 2) + outline-recent-prefix-beginning + (goto-char (match-beginning 2)))) + ;; We should be at the beginning of the buffer if the last + ;; condition failed. line-boundary-regexp doesn't cover topic + ;; at bob - Check for it. + ((looking-at outline-regexp) + (setq outline-recent-prefix-end (match-end 0) + outline-recent-prefix-beginning + (goto-char (match-beginning 0))))) + ) +;;;_ > outline-end-of-prefix () +(defun outline-end-of-prefix () + " Position cursor at beginning of header text." + (if (not (outline-goto-prefix)) + nil + (let ((match-data (match-data))) + (goto-char (match-end 0)) + (while (looking-at "[0-9]") (forward-char 1)) + (if (and (not (eolp)) (looking-at "\\s-")) (forward-char 1)) + (store-match-data match-data)) + ;; Reestablish where we are: + (outline-current-depth)) + ) +;;;_ > outline-back-to-current-heading () +(defun outline-back-to-current-heading () + " Move to heading line of current visible topic, or beginning of heading + if already on visible heading line." + (beginning-of-line) + (prog1 (or (outline-on-current-heading-p) + (and (re-search-backward (concat "^\\(" outline-regexp "\\)") + nil + 'move) + (setq outline-recent-prefix-end (match-end 1) + outline-recent-prefix-beginning (match-beginning 1)))) + (if (interactive-p) (outline-end-of-prefix)) + ) + ) +;;;_ > outline-pre-next-preface () +(defun outline-pre-next-preface () + "Skip forward to just before the next heading line. + + Returns that character position." + + (if (re-search-forward outline-line-boundary-regexp nil 'move) + (progn (goto-char (match-beginning 0)) + (setq outline-recent-prefix-end (match-end 2) + outline-recent-prefix-beginning (match-beginning 2)))) + ) +;;;_ > outline-end-of-current-subtree () +(defun outline-end-of-current-subtree () + " Put point at the end of the last leaf in the currently visible topic." + (interactive) + (outline-back-to-current-heading) + (let ((opoint (point)) + (level (outline-recent-depth))) + (outline-next-heading) + (while (and (not (eobp)) + (> (outline-recent-depth) level)) + (outline-next-heading)) + (if (not (eobp)) (forward-char -1)) + (if (memq (preceding-char) '(?\n ?\^M)) (forward-char -1)))) +;;;_ > outline-beginning-of-current-entry () +(defun outline-beginning-of-current-entry () + " Position the point at the beginning of the body of the current topic." + (interactive) + (outline-end-of-prefix)) +;;;_ > outline-beginning-of-current-entry () +(defun outline-end-of-current-entry () + " Position the point at the end of the current topic's entry." + (interactive) + (outline-show-entry) + (prog1 (outline-pre-next-preface) + (if (and (not (bobp))(looking-at "^$")) + (forward-char -1))) +) + +;;;_ : Depth-wise +;;;_ > outline-ascend-to-depth (depth) +(defun outline-ascend-to-depth (depth) + " Ascend to depth DEPTH, returning depth if successful, nil if not." + (if (and (> depth 0)(<= depth (outline-depth))) + (let ((last-good (point))) + (while (and (< depth (outline-depth)) + (setq last-good (point)) + (outline-beginning-of-level) + (outline-previous-heading))) + (if (= (outline-recent-depth) depth) + (progn (goto-char outline-recent-prefix-beginning) + depth) + (goto-char last-good) + nil)) + (if (interactive-p) (outline-end-of-prefix)) + ) + ) +;;;_ > outline-descend-to-depth (depth) +(defun outline-descend-to-depth (depth) + " Descend to depth DEPTH within current topic, returning depth if + successful, nil if not." + (let ((start-point (point)) + (start-depth (outline-depth))) + (while + (and (> (outline-depth) 0) + (not (= depth (outline-recent-depth))) ; ... not there yet + (outline-next-heading) ; ... go further + (< start-depth (outline-recent-depth)))) ; ... still in topic + (if (and (> (outline-depth) 0) + (= (outline-recent-depth) depth)) + depth + (goto-char start-point) + nil)) + ) +;;;_ > outline-up-current-level (arg &optional dont-complain) +(defun outline-up-current-level (arg &optional dont-complain) + " Move to the heading line of which the present line is a subheading. + With argument, move up ARG levels. Don't return an error if + second, optional argument DONT-COMPLAIN, is non-nil." + (interactive "p") + (outline-back-to-current-heading) + (let ((present-level (outline-recent-depth))) + ;; Loop for iterating arg: + (while (and (> (outline-recent-depth) 1) + (> arg 0) + (not (bobp))) + ;; Loop for going back over current or greater depth: + (while (and (not (< (outline-recent-depth) present-level)) + (outline-previous-visible-heading 1))) + (setq present-level (outline-current-depth)) + (setq arg (- arg 1))) + ) + (prog1 (if (<= arg 0) + outline-recent-prefix-beginning + (if (interactive-p) (outline-end-of-prefix)) + (if (not dont-complain) + (error "Can't ascend past outermost level."))) + (if (interactive-p) (outline-end-of-prefix))) + ) + +;;;_ : Linear +;;;_ > outline-next-visible-heading (arg) +(defun outline-next-visible-heading (arg) + " Move to the next visible heading line. + + With argument, repeats, backward if negative." + (interactive "p") + (if (< arg 0) (beginning-of-line) (end-of-line)) + (if (re-search-forward (concat "^\\(" outline-regexp "\\)") + nil + 'go + arg) + (progn (outline-end-of-prefix) + (setq outline-recent-prefix-end (match-end 1) + outline-recent-prefix-beginning (match-beginning 1)))) + ) +;;;_ > outline-previous-visible-heading (arg) +(defun outline-previous-visible-heading (arg) + " Move to the previous heading line. + + With argument, repeats or can move forward if negative. + A heading line is one that starts with a `*' (or that outline-regexp + matches)." + (interactive "p") + (outline-next-visible-heading (- arg)) + ) +;;;_ > outline-next-heading (&optional backward) +(defun outline-next-heading (&optional backward) + " Move to the heading for the topic (possibly invisible) before this one. + + Optional arg BACKWARD means search for most recent prior heading. + + Returns the location of the heading, or nil if none found." + + (if (and backward (bobp)) + nil + (if backward (outline-goto-prefix) + (if (and (bobp) (not (eobp))) + (forward-char 1))) + + (if (if backward + ;; searches are unbounded and return nil if failed: + (or (re-search-backward outline-line-boundary-regexp + nil + 0) + (looking-at outline-bob-regexp)) + (re-search-forward outline-line-boundary-regexp + nil + 0)) + (progn;; Got some valid location state - set vars: + (setq outline-recent-prefix-end + (or (match-end 2) outline-recent-prefix-end)) + (goto-char (setq outline-recent-prefix-beginning + (or (match-beginning 2) + outline-recent-prefix-beginning)))) + ) + ) + ) +;;;_ > outline-previous-heading () +(defun outline-previous-heading () + " Move to the next (possibly invisible) heading line. + + Optional repeat-count arg means go that number of headings. + + Return the location of the beginning of the heading, or nil if not found." + + (outline-next-heading t) + ) +;;;_ > outline-next-sibling (&optional backward) +(defun outline-next-sibling (&optional backward) + " Like outline-forward-current-level, but respects invisible topics. + + Go backward if optional arg BACKWARD is non-nil. + + Return depth if successful, nil otherwise." + + (if (and backward (bobp)) + nil + (let ((start-depth (outline-depth)) + (start-point (point)) + last-good) + (while (and (not (if backward (bobp) (eobp))) + (if backward (outline-previous-heading) + (outline-next-heading)) + (> (outline-recent-depth) start-depth))) + (if (and (not (eobp)) + (and (> (outline-depth) 0) + (= (outline-recent-depth) start-depth))) + outline-recent-prefix-beginning + (goto-char start-point) + nil) + ) + ) + ) +;;;_ > outline-previous-sibling (&optional arg) +(defun outline-previous-sibling (&optional arg) + " Like outline-forward-current-level, but goes backwards and respects + invisible topics. + + Optional repeat count means go number backward. + + Note that the beginning of a level is (currently) defined by this + implementation to be the first of previous successor topics of + equal or greater depth. + + Return depth if successful, nil otherwise." + (outline-next-sibling t) + ) +;;;_ > outline-beginning-of-level () +(defun outline-beginning-of-level () + " Go back to the first sibling at this level, visible or not." + (outline-end-of-level 'backward)) +;;;_ > outline-end-of-level (&optional backward) +(defun outline-end-of-level (&optional backward) + " Go to the last sibling at this level, visible or not." + + (while (outline-previous-sibling)) + (prog1 (outline-recent-depth) + (if (interactive-p) (outline-end-of-prefix))) +) +;;;_ > outline-forward-current-level (arg &optional backward) +(defun outline-forward-current-level (arg &optional backward) + " Position the point at the next heading of the same level, taking + optional repeat-count. + + Returns that position, else nil if is not found." + (interactive "p") + (outline-back-to-current-heading) + (let ((amt (if arg (if (< arg 0) + ;; Negative arg - invert direction. + (progn (setq backward (not backward)) + (abs arg)) + arg);; Positive arg - just use it. + 1)));; No arg - use 1: + (while (and (> amt 0) + (outline-next-sibling backward)) + (setq amt (1- amt))) + (if (interactive-p) (outline-end-of-prefix)) + (if (> amt 0) + (error "This is the %s topic on level %d." + (if backward "first" "last") + (outline-current-depth)) + t) + ) + ) +;;;_ > outline-backward-current-level (arg) +(defun outline-backward-current-level (arg) + " Position the point at the previous heading of the same level, taking + optional repeat-count. + + Returns that position, else nil if is not found." + (interactive "p") + (unwind-protect + (outline-forward-current-level arg t) + (outline-end-of-prefix)) +) + +;;;_ : Search with Dynamic Exposure (requires isearch-mode) +;;;_ = outline-search-reconceal +(defvar outline-search-reconceal nil + "Used for outline isearch provisions, to track whether current search +match was concealed outside of search. The value is the location of the +match, if it was concealed, regular if the entire topic was concealed, in +a list if the entry was concealed.") +;;;_ = outline-search-quitting +(defconst outline-search-quitting nil + "Variable used by isearch-terminate/outline-provisions and +isearch-done/outline-provisions to distinguish between a conclusion +and cancellation of a search.") + +;;;_ > outline-enwrap-isearch () +(defun outline-enwrap-isearch () + " Impose isearch-mode wrappers so isearch progressively exposes and + reconceals hidden topics when working in outline mode, but works + elsewhere. + + The function checks to ensure that the rebindings are done only once." + + ; Should isearch-mode be employed, + (if (or (not outline-enwrap-isearch-mode) + ; or are preparations already done? + (fboundp 'real-isearch-terminate)) + + ;; ... no - skip this all: + nil + + ;; ... yes: + + ; Ensure load of isearch-mode: + (if (or (and (fboundp 'isearch-mode) + (fboundp 'isearch-quote-char)) + (condition-case error + (load-library outline-enwrap-isearch-mode) + (file-error (message "Skipping isearch-mode provisions - %s '%s'" + (car (cdr error)) + (car (cdr (cdr error)))) + (sit-for 1) + ;; Inhibit subsequent tries and return nil: + (setq outline-enwrap-isearch-mode nil)))) + ;; Isearch-mode loaded, encapsulate specific entry points for + ;; outline dynamic-exposure business: + (progn + + ; stash crucial isearch-mode + ; funcs under known, private + ; names, then register wrapper + ; functions under the old + ; names, in their stead: + ; 'isearch-quit' is pre v 1.2: + (fset 'real-isearch-terminate + ; 'isearch-quit is pre v 1.2: + (or (if (fboundp 'isearch-quit) + (symbol-function 'isearch-quit)) + (if (fboundp 'isearch-abort) + ; 'isearch-abort' is v 1.2 and on: + (symbol-function 'isearch-abort)))) + (fset 'isearch-quit 'isearch-terminate/outline-provisions) + (fset 'isearch-abort 'isearch-terminate/outline-provisions) + (fset 'real-isearch-done (symbol-function 'isearch-done)) + (fset 'isearch-done 'isearch-done/outline-provisions) + (fset 'real-isearch-update (symbol-function 'isearch-update)) + (fset 'isearch-update 'isearch-update/outline-provisions) + (make-variable-buffer-local 'outline-search-reconceal)) + ) + ) + ) +;;;_ > outline-isearch-arrival-business () +(defun outline-isearch-arrival-business () + " Do outline business like exposing current point, if necessary, + registering reconcealment requirements in outline-search-reconceal + accordingly. + + Set outline-search-reconceal to nil if current point is not + concealed, to value of point if entire topic is concealed, and a + list containing point if only the topic body is concealed. + + This will be used to determine whether outline-hide-current-entry + or outline-hide-current-entry-completely will be necessary to + restore the prior concealment state." + + (if (and (boundp 'outline-mode) outline-mode) + (setq outline-search-reconceal + (if (outline-hidden-p) + (save-excursion + (if (re-search-backward outline-line-boundary-regexp nil 1) + ;; Nil value means we got to b-o-b - wouldn't need + ;; to advance. + (forward-char 1)) + ; We'll return point or list + ; containing point, depending + ; on concealment state of + ; topic prefix. + (prog1 (if (outline-hidden-p) (point) (list (point))) + ; And reveal the current + ; search target: + (outline-show-entry))))))) +;;;_ > outline-isearch-advancing-business () +(defun outline-isearch-advancing-business () + " Do outline business like deexposing current point, if necessary, + according to reconceal state registration." + (if (and (boundp 'outline-mode) outline-mode outline-search-reconceal) + (save-excursion + (if (listp outline-search-reconceal) + ;; Leave the topic visible: + (progn (goto-char (car outline-search-reconceal)) + (outline-hide-current-entry)) + ;; Rehide the entire topic: + (goto-char outline-search-reconceal) + (outline-hide-current-entry-completely)))) + ) +;;;_ > isearch-terminate/outline-provisions () +(defun isearch-terminate/outline-provisions () + (interactive) + (if (and (boundp 'outline-mode) + outline-mode + outline-enwrap-isearch-mode) + (outline-isearch-advancing-business)) + (let ((outline-search-quitting t) + (outline-search-reconceal nil)) + (real-isearch-terminate))) +;;;_ > isearch-done/outline-provisions () +(defun isearch-done/outline-provisions (&optional nopush) + (interactive) + (if (and (boundp 'outline-mode) + outline-mode + outline-enwrap-isearch-mode) + (progn (save-excursion + (if (and outline-search-reconceal + (not (listp outline-search-reconceal))) + ;; The topic was concealed - reveal it, its siblings, + ;; and any ancestors that are still concealed: + (progn + (message "(exposing destination)")(sit-for 0) + ;; Ensure target topic's siblings are exposed: + (outline-ascend-to-depth (1- (outline-current-depth))) + ;; Ensure that the target topic's ancestors are exposed + (while (outline-hidden-p) + (outline-show-current-children)) + (outline-show-current-children) + (outline-show-current-entry))) + (outline-isearch-arrival-business)) + (if (not (and (boundp 'outline-search-quitting) + outline-search-quitting)) + (outline-show-current-children)))) + (if nopush + ;; isearch-done in newer version of isearch mode takes arg: + (real-isearch-done nopush) + (real-isearch-done))) +;;;_ > isearch-update/outline-provisions () +(defun isearch-update/outline-provisions () + " Wrapper around isearch which exposes and conceals hidden outline + portions encountered in the course of searching." + (if (not (and (boundp 'outline-mode) + outline-mode + outline-enwrap-isearch-mode)) + ;; Just do the plain business: + (real-isearch-update) + + ;; Ah - provide for outline conditions: + (outline-isearch-advancing-business) + (real-isearch-update) + (cond (isearch-success (outline-isearch-arrival-business)) + ((not isearch-success) (outline-isearch-advancing-business))) + ) + ) + +;;;_ #5 Manipulation + +;;;_ : Topic Format Assessment +;;;_ > outline-solicit-alternate-bullet (depth &optional current-bullet) +(defun outline-solicit-alternate-bullet (depth &optional current-bullet) + + " Prompt for and return a bullet char as an alternative to the + current one, but offer one suitable for current depth DEPTH + as default." + + (let* ((default-bullet (or current-bullet + (outline-bullet-for-depth depth))) + (choice (solicit-char-in-string + (format "Select bullet: %s ('%s' default): " + outline-bullets-string + default-bullet) + (string-sans-char outline-bullets-string ?\\) + t))) + (if (string= choice "") default-bullet choice)) + ) +;;;_ > outline-sibling-index (&optional depth) +(defun outline-sibling-index (&optional depth) + " Item number of this prospective topic among it's siblings. + + If optional arg depth is greater than current depth, then we're + opening a new level, and return 0. + + If less than this depth, ascend to that depth and count..." + + (save-excursion + (cond ((and depth (<= depth 0) 0)) + ((or (not depth) (= depth (outline-depth))) + (let ((index 1)) + (while (outline-previous-sibling) (setq index (1+ index))) + index)) + ((< depth (outline-recent-depth)) + (outline-ascend-to-depth depth) + (outline-sibling-index)) + (0)))) +;;;_ > outline-distinctive-bullet (bullet) +(defun outline-distinctive-bullet (bullet) + " True if bullet is one of those on outline-distinctive-bullets-string." + (string-match (regexp-quote bullet) outline-distinctive-bullets-string)) +;;;_ > outline-numbered-type-prefix (&optional prefix) +(defun outline-numbered-type-prefix (&optional prefix) + " True if current header prefix bullet is numbered bullet." + (and outline-numbered-bullet + (string= outline-numbered-bullet + (if prefix + (outline-get-prefix-bullet prefix) + (outline-get-bullet))))) +;;;_ > outline-bullet-for-depth (&optional depth) +(defun outline-bullet-for-depth (&optional depth) + " Return outline topic bullet suited to DEPTH, or for current depth if none + specified." + ;; Find bullet in plain-bullets-string modulo DEPTH. + (if outline-stylish-prefixes + (char-to-string (aref outline-plain-bullets-string + (% (max 0 (- depth 2)) + outline-plain-bullets-string-len))) + outline-primary-bullet) + ) + +;;;_ : Topic Production +;;;_ > outline-make-topic-prefix (&optional prior-bullet +(defun outline-make-topic-prefix (&optional prior-bullet + new + depth + solicit + number-control + index) + ;; Depth null means use current depth, non-null means we're either + ;; opening a new topic after current topic, lower or higher, or we're + ;; changing level of current topic. + ;; Solicit dominates specified bullet-char. + " Generate a topic prefix suitable for optional arg DEPTH, or current + depth if not specified. + + All the arguments are optional. + + PRIOR-BULLET indicates the bullet of the prefix being changed, or + nil if none. This bullet may be preserved (other options + notwithstanding) if it is on the outline-distinctive-bullets-string, + for instance. + + Second arg NEW indicates that a new topic is being opened after the + topic at point, if non-nil. Default bullet for new topics, eg, may + be set (contingent to other args) to numbered bullets if previous + sibling is one. The implication otherwise is that the current topic + is being adjusted - shifted or rebulleted - and we don't consider + bullet or previous sibling. + + Third arg DEPTH forces the topic prefix to that depth, regardless of + the current topics' depth. + + Fourth arg SOLICIT non-nil provokes solicitation from the user of a + choice among the valid bullets. (This overrides other all the + options, including, eg, a distinctive PRIOR-BULLET.) + + Fifth arg, NUMBER-CONTROL, matters only if 'outline-numbered-bullet' + is non-nil *and* soliciting was not explicitly invoked. Then + NUMBER-CONTROL non-nil forces prefix to either numbered or + denumbered format, depending on the value of the sixth arg, INDEX. + + (Note that NUMBER-CONTROL does *not* apply to level 1 topics. Sorry...) + + If NUMBER-CONTROL is non-nil and sixth arg INDEX is non-nil then + the prefix of the topic is forced to be numbered. Non-nil + NUMBER-CONTROL and nil INDEX forces non-numbered format on the + bullet. Non-nil NUMBER-CONTROL and non-nil, non-number INDEX means + that the index for the numbered prefix will be derived, by counting + siblings back to start of level. If INDEX is a number, then that + number is used as the index for the numbered prefix (allowing, eg, + sequential renumbering to not requre this function counting back the + index for each successive sibling)." + + ;; The options are ordered in likely frequence of use, most common + ;; highest, least lowest. Ie, more likely to be doing prefix + ;; adjustments than soliciting, and yet more than numbering. + ;; Current prefix is least dominant, but most likely to be commonly + ;; specified... + + (let* (body + numbering + denumbering + (depth (or depth (outline-depth))) + (header-lead outline-header-prefix) + (bullet-char + + ;; Getting value for bullet char is practically the whole job: + + (cond + ; Simplest situation - level 1: + ((<= depth 1) (setq header-lead "") outline-primary-bullet) + ; Simple, too: all asterisks: + (outline-old-style-prefixes + ;; Cheat - make body the whole thing, null out header-lead and + ;; bullet-char: + (setq body (make-string depth + (string-to-char outline-primary-bullet))) + (setq header-lead "") + "") + + ;; (Neither level 1 nor old-style, so we're space padding. + ;; Sneak it in the condition of the next case, whatever it is.) + + ;; Solicitation overrides numbering and other cases: + ((progn (setq body (make-string (- depth 2) ?\ )) + ;; The actual condition: + solicit) + (let* ((got (outline-solicit-alternate-bullet depth))) + ;; Gotta check whether we're numbering and got a numbered bullet: + (setq numbering (and outline-numbered-bullet + (not (and number-control (not index))) + (string= got outline-numbered-bullet))) + ;; Now return what we got, regardless: + got)) + + ;; Numbering invoked through args: + ((and outline-numbered-bullet number-control) + (if (setq numbering (not (setq denumbering (not index)))) + outline-numbered-bullet + (if (and current-bullet + (not (string= outline-numbered-bullet + current-bullet))) + current-bullet + (outline-bullet-for-depth depth)))) + + ;;; Neither soliciting nor controlled numbering ;;; + ;;; (may be controlled denumbering, tho) ;;; + + ;; Check wrt previous sibling: + ((and new ; only check for new prefixes + (<= depth (outline-depth)) + outline-numbered-bullet ; ... & numbering enabled + (not denumbering) + (let ((sibling-bullet + (save-excursion + ;; Locate correct sibling: + (or (>= depth (outline-depth)) + (outline-ascend-to-depth depth)) + (outline-get-bullet)))) + (if (and sibling-bullet + (string= outline-numbered-bullet sibling-bullet)) + (setq numbering sibling-bullet))))) + + ;; Distinctive prior bullet? + ((and prior-bullet + (outline-distinctive-bullet prior-bullet) + ;; Either non-numbered: + (or (not (and outline-numbered-bullet + (string= prior-bullet outline-numbered-bullet))) + ;; or numbered, and not denumbering: + (setq numbering (not denumbering))) + ;; Here 'tis: + prior-bullet)) + + ;; Else, standard bullet per depth: + ((outline-bullet-for-depth depth))))) + + (concat header-lead + body + bullet-char + (if numbering + (format "%d" (cond ((and index (numberp index)) index) + (new (1+ (outline-sibling-index depth))) + ((outline-sibling-index)))))) + ) + ) +;;;_ > open-topic (relative-depth &optional before) +(defun open-topic (relative-depth &optional before) + " Open a new topic at depth DEPTH. New topic is situated after current + one, unless optional flag BEFORE is non-nil, or unless current line + is complete empty (not even whitespace), in which case open is done + on current line. + + Nuances: + + - Creation of new topics is with respect to the visible topic + containing the cursor, regardless of intervening concealed ones. + + - New headers are generally created after/before the body of a + topic. However, they are created right at cursor location if the + cursor is on a blank line, even if that breaks the current topic + body. This is intentional, to provide a simple means for + deliberately dividing topic bodies. + + - Double spacing of topic lists is preserved. Also, the first + level two topic is created double-spaced (and so would be + subsequent siblings, if that's left intact). Otherwise, + single-spacing is used. + + - Creation of sibling or nested topics is with respect to the topic + you're starting from, even when creating backwards. This way you + can easily create a sibling in front of the current topic without + having to go to it's preceeding sibling, and then open forward + from there." + + (let* ((depth (+ (outline-current-depth) relative-depth)) + (opening-on-blank (if (looking-at "^\$") + (not (setq before nil)))) + opening-numbered ; Will get while computing ref-topic, below + ref-depth ; Will get while computing ref-topic, next + (ref-topic (save-excursion + (cond ((< relative-depth 0) + (outline-ascend-to-depth depth)) + ((>= relative-depth 1) nil) + (t (outline-back-to-current-heading))) + (setq ref-depth (outline-recent-depth)) + (setq opening-numbered + (save-excursion + (and outline-numbered-bullet + (or (<= relative-depth 0) + (outline-descend-to-depth depth)) + (if (outline-numbered-type-prefix) + outline-numbered-bullet)))) + (point))) + dbl-space + doing-beginning + ) + + (if (not opening-on-blank) + ; Positioning and vertical + ; padding - only if not + ; opening-on-blank: + (progn + (goto-char ref-topic) + (setq dbl-space ; Determine double space action: + (or (and (not (> relative-depth 0)) + ;; not descending, + (save-excursion + ;; preceeded by a blank line? + (forward-line -1) + (looking-at "^\\s-*$"))) + (and (= ref-depth 1) + (or before + (= depth 1) + (save-excursion + ;; Don't already have following + ;; vertical padding: + (not (outline-pre-next-preface))))))) + + ; Position to prior heading, + ; if inserting backwards: + (if before (progn (outline-back-to-current-heading) + (setq doing-beginning (bobp)) + (if (and (not (outline-previous-sibling)) + (not (bobp))) + (outline-previous-heading)))) + + (if (and (<= depth ref-depth) + (= ref-depth (outline-current-depth))) + ;; Not going inwards, don't snug up: + (if doing-beginning + (open-line (if dbl-space 2 1)) + (outline-end-of-current-subtree)) + ;; Going inwards - double-space if first offspring is, + ;; otherwise snug up. + (end-of-line) ; So we skip any concealed progeny. + (outline-pre-next-preface) + (if (bolp) + ;; Blank lines between current header body and next + ;; header - get to last substantive (non-white-space) + ;; line in body: + (re-search-backward "[^ \t\n]" nil t)) + (if (save-excursion + (outline-next-heading) + (if (> (outline-recent-depth) ref-depth) + ;; This is an offspring. + (progn (forward-line -1) + (looking-at "^\\s-*$")))) + (progn (forward-line 1) + (open-line 1))) + (end-of-line)) + ;;(if doing-beginning (goto-char doing-beginning)) + (if (not (bobp)) (newline (if dbl-space 2 1))) + )) + (insert-string (concat (outline-make-topic-prefix opening-numbered + t + depth) + " ")) + + ;;(if doing-beginning (save-excursion (newline (if dbl-space 2 1)))) + + + (outline-rebullet-heading nil ;;; solicit + depth ;;; depth + nil ;;; number-control + nil ;;; index + t) (end-of-line) + ) + ) +;;;_ > open-subtopic (arg) +(defun open-subtopic (arg) + " Open new topic header at deeper level than the current one. + + Negative universal arg means to open deeper, but place the new topic + prior to the current one." + (interactive "p") + (open-topic 1 (> 0 arg))) +;;;_ > open-sibtopic (arg) +(defun open-sibtopic (arg) + " Open new topic header at same level as the current one. Negative + universal arg means to place the new topic prior to the current + one." + (interactive "p") + (open-topic 0 (> 0 arg))) +;;;_ > open-supertopic (arg) +(defun open-supertopic (arg) + " Open new topic header at shallower level than the current one. + Negative universal arg means to open shallower, but place the new + topic prior to the current one." + + (interactive "p") + (open-topic -1 (> 0 arg))) + +;;;_ : Outline Alteration +;;;_ . Topic Form Modification +;;;_ > outline-reindent-body (old-depth new-depth) +(defun outline-reindent-body (old-depth new-depth) + " Reindent body lines which were indented at old-depth to new-depth. + + Note that refill of indented paragraphs is not done, and tabs are + not accomodated. ('untabify' your outline if you want to preserve + hanging body indents.)" + + (save-excursion + (save-restriction + (outline-goto-prefix) + (forward-char 1) + (let* ((old-spaces-expr (make-string (1+ old-depth) ?\ )) + (new-spaces-expr (concat (make-string (1+ new-depth) ?\ ) + ;; spaces followed by non-space: + "\\1"))) + (while (and (re-search-forward "[\C-j\C-m]" nil t) + (not (looking-at outline-regexp))) + (if (looking-at old-spaces-expr) + (replace-match new-spaces-expr))))))) +;;;_ > outline-rebullet-current-heading (arg) +(defun outline-rebullet-current-heading (arg) + " Like non-interactive version 'outline-rebullet-heading', but work on + (only) visible heading containing point. + + With repeat count, solicit for bullet." + (interactive "P") + (save-excursion (outline-back-to-current-heading) + (outline-end-of-prefix) + (outline-rebullet-heading (not arg) ;;; solicit + nil ;;; depth + nil ;;; number-control + nil ;;; index + t) ;;; do-successors + ) + ) +;;;_ > outline-rebullet-heading (&optional solicit ...) +(defvar current-bullet nil + "Variable local to outline-rebullet-heading,but referenced by +outline-make-topic-prefix, also. Should be resolved with explicitly +parameterized communication between the two, if suitable.") +(defun outline-rebullet-heading (&optional solicit + new-depth + number-control + index + do-successors) + + " Adjust bullet of current topic prefix. + + All args are optional. + + If SOLICIT is non-nil then the choice of bullet is solicited from + user. Otherwise the distinctiveness of the bullet or the topic + depth determines it. + + Second arg DEPTH forces the topic prefix to that depth, regardless + of the topic's current depth. + + Third arg NUMBER-CONTROL can force the prefix to or away from + numbered form. It has effect only if 'outline-numbered-bullet' is + non-nil and soliciting was not explicitly invoked (via first arg). + Its effect, numbering or denumbering, then depends on the setting + of the forth arg, INDEX. + + If NUMBER-CONTROL is non-nil and forth arg INDEX is nil, then the + prefix of the topic is forced to be non-numbered. Null index and + non-nil NUMBER-CONTROL forces denumbering. Non-nil INDEX (and + non-nil NUMBER-CONTROL) forces a numbered-prefix form. If non-nil + INDEX is a number, then that number is used for the numbered + prefix. Non-nil and non-number means that the index for the + numbered prefix will be derived by outline-make-topic-prefix. + + Fifth arg DO-SUCCESSORS t means re-resolve count on succeeding + siblings. + + Cf vars 'outline-stylish-prefixes', 'outline-old-style-prefixes', + and 'outline-numbered-bullet', which all affect the behavior of + this function." + + (let* ((current-depth (outline-depth)) + (new-depth (or new-depth current-depth)) + (mb outline-recent-prefix-beginning) + (me outline-recent-prefix-end) + (current-bullet (buffer-substring (- me 1) me)) + (new-prefix (outline-make-topic-prefix current-bullet + nil + new-depth + solicit + number-control + index))) + + ;; Don't need to reinsert identical one: + (if (and (= current-depth new-depth) + (string= current-bullet + (substring new-prefix (1- (length new-prefix))))) + t + + ;; New prefix probably different from old: + ;; get rid of old one: + (delete-region mb me) + (goto-char mb) + ;; Dispense with number if numbered-bullet prefix: + (if (and outline-numbered-bullet + (string= outline-numbered-bullet current-bullet) + (looking-at "[0-9]+")) + (delete-region (match-beginning 0)(match-end 0))) + + ;; Put in new prefix: + (insert-string new-prefix) + ) + + ;; Reindent the body if elected and depth changed: + (if (and outline-reindent-bodies + (not (= new-depth current-depth))) + (outline-reindent-body current-depth new-depth)) + + ;; Recursively rectify successive siblings if selected: + (if do-successors + (save-excursion + (while (outline-next-sibling) + (setq index + (cond ((numberp index) (1+ index)) + ((not number-control) (outline-sibling-index)))) + (if (outline-numbered-type-prefix) + (outline-rebullet-heading nil ;;; solicit + new-depth ;;; new-depth + number-control;;; number-control + index ;;; index + nil))))) ;;;(dont!)do-successors + ) + ) +;;;_ > outline-rebullet-topic (arg) +(defun outline-rebullet-topic (arg) + " Like outline-rebullet-topic-grunt, but start from topic visible at point. + Descends into invisible as well as visible topics, however. + + With repeat count, shift topic depth by that amount." + (interactive "P") + (let ((start-col (current-column)) + (was-eol (eolp))) + (save-excursion + ;; Normalize arg: + (cond ((null arg) (setq arg 0)) + ((listp arg) (setq arg (car arg)))) + ;; Fill the user in, in case we're shifting a big topic: + (if (not (zerop arg)) (message "Shifting...")) + (outline-back-to-current-heading) + (if (<= (+ (outline-recent-depth) arg) 0) + (error "Attempt to shift topic below level 1")) + (outline-rebullet-topic-grunt arg) + (if (not (zerop arg)) (message "Shifting... done."))) + (move-to-column (max 0 (+ start-col arg)))) + ) +;;;_ > outline-rebullet-topic-grunt (&optional relative-depth ...) +(defun outline-rebullet-topic-grunt (&optional relative-depth + starting-depth + starting-point + index + do-successors) + + " Rebullet the topic at point, visible or invisible, and all + contained subtopics. See outline-rebullet-heading for rebulleting + behavior. + + All arguments are optional. + + First arg RELATIVE-DEPTH means to shift the depth of the entire + topic that amount. + + The rest of the args are for internal recursive use by the function + itself. The are STARTING-DEPTH, STARTING-POINT, and INDEX." + + (let* ((relative-depth (or relative-depth 0)) + (new-depth (outline-depth)) + (starting-depth (or starting-depth new-depth)) + (on-starting-call (null starting-point)) + (index (or index + ;; Leave index null on starting call, so rebullet-heading + ;; calculates it at what might be new depth: + (and (or (zerop relative-depth) + (not on-starting-call)) + (outline-sibling-index)))) + (moving-outwards (< 0 relative-depth)) + (starting-point (or starting-point (point)))) + + ;; Sanity check for excessive promotion done only on starting call: + (and on-starting-call + moving-outwards + (> 0 (+ starting-depth relative-depth)) + (error "Attempt to shift topic out beyond level 1.")) ;;; ====> + + (cond ((= starting-depth new-depth) + ;; We're at depth to work on this one: + (outline-rebullet-heading nil ;;; solicit + (+ starting-depth ;;; starting-depth + relative-depth) + nil ;;; number + index ;;; index + ;; Every contained topic will get hit, + ;; and we have to get to outside ones + ;; deliberately: + nil) ;;; do-successors + ;; ... and work on subsequent ones which are at greater depth: + (setq index 0) + (outline-next-heading) + (while (and (not (eobp)) + (< starting-depth (outline-recent-depth))) + (setq index (1+ index)) + (outline-rebullet-topic-grunt relative-depth ;;; relative-depth + (1+ starting-depth);;;starting-depth + starting-point ;;; starting-point + index))) ;;; index + + ((< starting-depth new-depth) + ;; Rare case - subtopic more than one level deeper than parent. + ;; Treat this one at an even deeper level: + (outline-rebullet-topic-grunt relative-depth ;;; relative-depth + new-depth ;;; starting-depth + starting-point ;;; starting-point + index))) ;;; index + + (if on-starting-call + (progn + ;; Rectify numbering of former siblings of the adjusted topic, + ;; if topic has changed depth + (if (or do-successors + (and (not (zerop relative-depth)) + (or (= (outline-recent-depth) starting-depth) + (= (outline-recent-depth) (+ starting-depth + relative-depth))))) + (outline-rebullet-heading nil nil nil nil t)) + ;; Now rectify numbering of new siblings of the adjusted topic, + ;; if depth has been changed: + (progn (goto-char starting-point) + (if (not (zerop relative-depth)) + (outline-rebullet-heading nil nil nil nil t))))) + ) + ) +;;;_ > outline-number-siblings (&optional denumber) +(defun outline-number-siblings (&optional denumber) + " Assign numbered topic prefix to this topic and its siblings. + + With universal argument, denumber - assign default bullet to this + topic and its siblings. + + With repeated universal argument (`^U^U'), solicit bullet for each + rebulleting each topic at this level." + + (interactive "P") + + (save-excursion + (outline-back-to-current-heading) + (outline-beginning-of-level) + (let ((index (if (not denumber) 1)) + (use-bullet (equal '(16) denumber)) + (more t)) + (while more + (outline-rebullet-heading use-bullet ;;; solicit + nil ;;; depth + t ;;; number-control + index ;;; index + nil) ;;; do-successors + (if index (setq index (1+ index))) + (setq more (outline-next-sibling))) + ) + ) + ) +;;;_ > outline-shift-in (arg) +(defun outline-shift-in (arg) + " Decrease prefix depth of current heading and any topics collapsed + within it." + (interactive "p") + (outline-rebullet-topic arg)) +;;;_ > outline-shift-out (arg) +(defun outline-shift-out (arg) + " Decrease prefix depth of current heading and any topics collapsed + within it." + (interactive "p") + (outline-rebullet-topic (* arg -1))) +;;;_ . Surgery (kill-ring) functions with special provisions for outlines: +;;;_ > outline-kill-line (&optional arg) +(defun outline-kill-line (&optional arg) + " Kill line, adjusting subsequent lines suitably for outline mode." + + (interactive "*P") + (if (not (and + (boundp 'outline-mode) outline-mode ; active outline mode, + outline-numbered-bullet ; numbers may need adjustment, + (bolp) ; may be clipping topic head, + (looking-at outline-regexp))) ; are clipping topic head. + ;; Above conditions do not obtain - just do a regular kill: + (kill-line arg) + ;; Ah, have to watch out for adjustments: + (let* ((depth (outline-depth)) + (ascender depth)) + (kill-line arg) + (sit-for 0) + (save-excursion + (if (not (looking-at outline-regexp)) + (outline-next-heading)) + (if (> (outline-depth) depth) + ;; An intervening parent was removed from after a subtree: + (setq depth (outline-recent-depth))) + (while (and (> (outline-depth) 0) + (> (outline-recent-depth) ascender) + (outline-ascend-to-depth (setq ascender + (1- ascender))))) + ;; Have to try going forward until we find another at + ;; desired depth: + (if (and outline-numbered-bullet + (outline-descend-to-depth depth)) + (outline-rebullet-heading nil ;;; solicit + depth ;;; depth + nil ;;; number-control + nil ;;; index + t) ;;; do-successors + ) + ) + ) + ) + ) +;;;_ > outline-kill-topic () +(defun outline-kill-topic () + " Kill topic together with subtopics." + + ;; Some finagling is done to make complex topic kills appear faster + ;; than they actually are. A redisplay is performed immediately + ;; after the region is disposed of, though the renumbering process + ;; has yet to be performed. This means that there may appear to be + ;; a lag *after* the kill has been performed. + + (interactive) + (let* ((beg (outline-back-to-current-heading)) + (depth (outline-recent-depth))) + (outline-end-of-current-subtree) + (if (not (eobp)) + (forward-char 1)) + (kill-region beg (point)) + (sit-for 0) + (save-excursion + (if (and outline-numbered-bullet + (outline-descend-to-depth depth)) + (outline-rebullet-heading nil ;;; solicit + depth ;;; depth + nil ;;; number-control + nil ;;; index + t) ;;; do-successors + ) + ) + ) + ) +;;;_ > outline-yank (&optional arg) +(defun outline-yank (&optional arg) + " Like regular yank, except does depth adjustment of yanked topics, when: + + 1 the stuff being yanked starts with a valid outline header prefix, and + 2 it is being yanked at the end of a line which consists of only a valid + topic prefix. + + If these two conditions hold then the depth of the yanked topics + are all adjusted the amount it takes to make the first one at the + depth of the header into which it's being yanked. + + The point is left in from of yanked, adjusted topics, rather than + at the end (and vice-versa with the mark). Non-adjusted yanks, + however, (ones that don't qualify for adjustment) are handled + exactly like normal yanks. + + Outline-yank-pop is used with outline-yank just as normal yank-pop + is used with normal yank in non-outline buffers." + + (interactive "*P") + (setq this-command 'yank) + (if (not (and (boundp 'outline-mode) outline-mode)) + + ;; Outline irrelevant - just do regular yank: + (yank arg) + + ;; Outline *is* relevant: + (let ((beginning (point)) + topic-yanked + established-depth) ; Depth of the prefix into which we're yanking. + ;; Get current depth and numbering ... Oops, not doing anything + ;; with the number just yet... + (if (and (eolp) + (save-excursion (beginning-of-line) + (looking-at outline-regexp))) + (setq established-depth (- (match-end 0) (match-beginning 0)))) + (yank arg) + (exchange-dot-and-mark) + (if (and established-depth ; the established stuff qualifies. + ;; The yanked stuff also qualfies - is topic(s): + (looking-at (concat "\\(" outline-regexp "\\)"))) + ;; Ok, adjust the depth of the yanked stuff. Note that the + ;; stuff may have more than a single root, so we have to + ;; iterate over all the top level ones yanked, and do them in + ;; such a way that the adjustment of one new one won't affect + ;; any of the other new ones. We use the focus of the + ;; narrowed region to successively exclude processed siblings. + (let* ((yanked-beg (match-beginning 1)) + (yanked-end (match-end 1)) + (yanked-bullet (buffer-substring (1- yanked-end) yanked-end)) + (yanked-depth (- yanked-end yanked-beg)) + (depth-diff (- established-depth yanked-depth)) + done + (more t)) + (setq topic-yanked t) + (save-excursion + (save-restriction + (narrow-to-region yanked-beg (mark)) + ;; First trim off excessive blank line at end, if any: + (goto-char (point-max)) + (if (looking-at "^$") (delete-char -1)) + (goto-char (point-min)) + ;; Work backwards, with each shallowest level, + ;; successively excluding the last processed topic + ;; from the narrow region: + (goto-char (point-max)) + (while more + (outline-back-to-current-heading) + ;; go as high as we can in each bunch: + (while (outline-ascend-to-depth + (1- (outline-depth)))) + (save-excursion + (outline-rebullet-topic-grunt depth-diff + (outline-depth) + (point))) + (if (setq more (not (bobp))) + (progn (widen) + (forward-char -1) + (narrow-to-region yanked-beg (point))))))) + ;; Preserve new bullet if it's a distinctive one, otherwise + ;; use old one: + (if (string-match yanked-bullet outline-distinctive-bullets-string) + (delete-region (save-excursion + (beginning-of-line) + (point)) + yanked-beg) + (delete-region yanked-beg (+ yanked-beg established-depth)) + ;; and extraneous digits and a space: + (while (looking-at "[0-9]") (delete-char 1)) + (if (looking-at " ") (delete-char 1)) + ) + (goto-char yanked-beg) + ) + ;; Not established-depth or looking-at... + (setq topic-yanked (looking-at outline-regexp)) + (exchange-dot-and-mark)) + (if (and topic-yanked outline-numbered-bullet) + (progn + ;; Renumber, in case necessary: + (sit-for 0) + (save-excursion + (goto-char beginning) + (if (outline-goto-prefix) + (outline-rebullet-heading nil ;;; solicit + (outline-depth) ;;; depth + nil ;;; number-control + nil ;;; index + t) ;;; do-successors + ) + ) + ) + ) + ) + ) + ) +;;;_ > outline-yank-pop (&optional arg) +(defun outline-yank-pop (&optional arg) + " Just like yank-pop, but works like outline-yank when popping + topics just after fresh outline prefixes. Adapts level of popped + stuff to level of fresh prefix." + + (interactive "*p") + (if (not (eq last-command 'yank)) + (error "Previous command was not a yank")) + (setq this-command 'yank) + (delete-region (point) (mark)) + (rotate-yank-pointer arg) + (outline-yank) + ) + +;;;_ : Specialty bullet functions +;;;_ . File Cross references +;;;_ > outline-resolve-xref () +(defun outline-resolve-xref () + " Pop to file associated with current heading, if it has an xref bullet + (according to setting of 'outline-file-xref-bullet')." + (interactive) + (if (not outline-file-xref-bullet) + (error + "outline cross references disabled - no 'outline-file-xref-bullet'") + (if (not (string= (outline-current-bullet) outline-file-xref-bullet)) + (error "current heading lacks cross-reference bullet '%s'" + outline-file-xref-bullet) + (let (file-name) + (save-excursion + (let* ((text-start outline-recent-prefix-end) + (heading-end (progn (outline-pre-next-preface) + (point)))) + (goto-char text-start) + (setq file-name + (if (re-search-forward "\\s-\\(\\S-*\\)" heading-end t) + (buffer-substring (match-beginning 1) (match-end 1)))))) + (setq file-name + (if (not (= (aref file-name 0) ?:)) + (expand-file-name file-name) + ; A registry-files ref, strip the ':' + ; and try to follow it: + (let ((reg-ref (reference-registered-file + (substring file-name 1) nil t))) + (if reg-ref (car (cdr reg-ref)))))) + (if (or (file-exists-p file-name) + (if (file-writable-p file-name) + (y-or-n-p (format "%s not there, create one? " + file-name)) + (error "%s not found and can't be created" file-name))) + (condition-case failure + (find-file-other-window file-name) + (error failure)) + (error "%s not found" file-name)) + ) + ) + ) + ) +;;;_ > outline-to-entry-end - Unmaintained compatability - ignore this! +;------------------------------------------------------------------- +; Something added solely for use by a "smart menu" package someone got +; off the net. I have no idea whether this is appropriate code. + +(defvar next-entry-exists nil "Used by outline-to-entry-end, dunno why.") +(defun outline-to-entry-end (&optional include-sub-entries curr-entry-level) + " Go to end of whole entry if optional INCLUDE-SUB-ENTRIES is non-nil. + CURR-ENTRY-LEVEL is an integer representing the length of the current level + string which matched to 'outline-regexp'. If INCLUDE-SUB-ENTRIES is nil, + CURR-ENTRY-LEVEL is not needed." + (while (and (setq next-entry-exists + (re-search-forward outline-regexp nil t)) + include-sub-entries + (save-excursion + (beginning-of-line) + (> (outline-depth) curr-entry-level)))) + (if next-entry-exists + (progn (beginning-of-line) (point)) + (goto-char (point-max)))) +;;; Outline topic prefix and level adjustment funcs: + +;;;_ #6 miscellaneous +;;;_ > outline-copy-exposed (&optional workbuf) +(defun outline-copy-exposed (&optional workbuf) + " Duplicate buffer to other buffer, sans hidden stuff. + + Without repeat count, this simple-minded function just generates + the new buffer by concatenating the current buffer name with \" + exposed\", and doing a 'get-buffer' on it." + + (interactive) + (if (not workbuf) (setq workbuf (concat (buffer-name) " exposed"))) + (let ((buf (current-buffer))) + (if (not (get-buffer workbuf)) + (generate-new-buffer workbuf)) + (pop-to-buffer workbuf) + (erase-buffer) + (insert-buffer buf) + (replace-regexp "\^M[^\^M\^J]*" "") + (goto-char (point-min)) + ) + ) +;;;_ > outlineify-sticky () +(defun outlineify-sticky (&optional arg) + " Activate outline mode and establish file eval to set initial exposure. + + Invoke with a string argument to designate a string to prepend to + topic prefixs, or with a universal argument to be prompted for the + string to be used. Suitable defaults are provided for lisp, + emacs-lisp, c, c++, awk, sh, csh, and perl modes." + + (interactive "P") (outline-mode t) + (cond (arg + (if (stringp arg) + ;; Use arg as the header-prefix: + (outline-lead-with-comment-string arg) + ;; Otherwise, let function solicit string: + (setq arg (outline-lead-with-comment-string)))) + ((member major-mode '(emacs-lisp-mode lisp-mode)) + (setq arg (outline-lead-with-comment-string ";;;_"))) + ((member major-mode '(awk-mode csh-mode sh-mode perl-mode)) + ;; Bare '#' (ie, not '#_') so we don't break the magic number: + (setq arg (outline-lead-with-comment-string "#"))) + ((eq major-mode 'c++-mode) + (setq arg (outline-lead-with-comment-string "//_"))) + ((eq major-mode 'c-mode) + ;; User's will have to know to close off the comments: + (setq arg (outline-lead-with-comment-string "/*_")))) + (let* ((lead-prefix (format "%s%s" + (concat outline-header-prefix (if arg " " "")) + outline-primary-bullet)) + (lead-line (format "%s%s %s\n%s %s\n %s %s %s" + (if arg outline-header-prefix "") + outline-primary-bullet + "Local emacs vars." + "'(This topic sets initial outline exposure" + "of the file when loaded by emacs," + "Encapsulate it in comments if" + "file is a program" + "otherwise ignore it,"))) + + (save-excursion + ; Put a topic at the top, if + ; none there already: + (goto-char (point-min)) + (if (not (looking-at outline-regexp)) + (insert-string + (if (not arg) outline-primary-bullet + (format "%s%s\n" outline-header-prefix outline-primary-bullet)))) + + ; File-vars stuff, at the bottom: + (goto-char (point-max)) + ; Insert preamble: + (insert-string (format "\n\n%s\n%s %s %s\n%s %s " + lead-line + lead-prefix + "local" + "variables:" + lead-prefix + "eval:")) + ; Insert outline-mode activation: + (insert-string + (format "%s\n\t\t%s\n\t\t\t%s\n" + "(condition-case err" + "(save-excursion" + "(outline-mode t)")) + ; Conditionally insert prefix + ; leader customization: + (if arg (insert-string (format "\t\t\t(%s \"%s\")\n" + "outline-lead-with-comment-string" + arg))) + ; Insert ammouncement and + ; exposure control: + (insert-string + (format "\t\t\t%s %s\n\t\t\t%s %s\n\t\t%s %s" + "(message \"Adjusting '%s' visibility\"" + "(buffer-name))" + "(goto-char 0)" + "(outline-exposure -1 0))" + "(error (message " + "\"Failed file var 'allout' provisions\")))")) + ; Insert postamble: + (insert-string (format "\n%s End: )\n" + lead-prefix))))) +;;;_ > solicit-char-in-string (prompt string &optional do-defaulting) +(defun solicit-char-in-string (prompt string &optional do-defaulting) + " Solicit (with first arg PROMPT) choice of a character from string STRING. + + Optional arg DO-DEFAULTING indicates to accept empty input (CR)." + + (let ((new-prompt prompt) + got) + + (while (not got) + (message "%s" new-prompt) + + ;; We do our own reading here, so we can circumvent, eg, special + ;; treatment for '?' character. (Might oughta change minibuffer + ;; keymap instead, oh well.) + (setq got + (char-to-string (let ((cursor-in-echo-area t)) (read-char)))) + + (if (null (string-match got string)) + (if (and do-defaulting (string= got "\^M")) + ;; We're defaulting, return null string to indicate that: + (setq got "") + ;; Failed match and not defaulting, + ;; set the prompt to give feedback, + (setq new-prompt (concat prompt + got + " ...pick from: " + string + "")) + ;; and set loop to try again: + (setq got nil)) + ;; Got a match - give feedback: + (message ""))) + ;; got something out of loop - return it: + got) + ) +;;;_ > string-sans-char (string char) +(defun string-sans-char (string char) + " Return a copy of STRING that lacks all instances of CHAR." + (cond ((string= string "") "") + ((= (aref string 0) char) (string-sans-char (substring string 1) char)) + ((concat (substring string 0 1) + (string-sans-char (substring string 1) char))))) + +;;;_* Local emacs vars. +'( +Local variables: +eval: (save-excursion + (if (not (condition-case err (outline-mode t) + (wrong-number-of-arguments nil))) + (progn + (message + "Allout outline-mode not loaded, not adjusting buffer exposure") + (sit-for 1)) + (message "Adjusting '%s' visibility" (buffer-name)) + (outline-lead-with-comment-string ";;;_") + (goto-char 0) + (outline-exposure (-1 () () () 1) 0))) +End: +) +