1 ;;; org-toc.el --- Table of contents for Org-mode buffer
3 ;; Copyright 2007-2013 Free Software Foundation, Inc.
5 ;; Author: Bastien Guerry <bzg AT gnu DOT org>
6 ;; Keywords: Org table of contents
7 ;; Homepage: http://www.cognition.ens.fr/~guerry/u/org-toc.el
10 ;; This file is not part of GNU Emacs.
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 3, or (at your option)
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program; if not, write to the Free Software
24 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 ;; This library implements a browsable table of contents for Org files.
30 ;; Put this file into your load-path and the following into your ~/.emacs:
40 (defvar org-toc-base-buffer nil
)
41 (defvar org-toc-columns-shown nil
)
42 (defvar org-toc-odd-levels-only nil
)
43 (defvar org-toc-config-alist nil
)
44 (defvar org-toc-cycle-global-status nil
)
45 (defalias 'org-show-table-of-contents
'org-toc-show
)
48 "Options concerning the browsable table of contents of Org-mode."
52 (defcustom org-toc-default-depth
1
53 "Default depth when invoking `org-toc-show' without argument."
56 (const :tag
"same as base buffer" nil
)
57 (integer :tag
"level")))
59 (defcustom org-toc-follow-mode nil
60 "Non-nil means navigating through the table of contents will
61 move the point in the Org buffer accordingly."
65 (defcustom org-toc-info-mode nil
66 "Non-nil means navigating through the table of contents will
67 show the properties for the current headline in the echo-area."
71 (defcustom org-toc-show-subtree-mode nil
72 "Non-nil means show subtree when going to headline or following
73 it while browsing the table of contents."
76 (const :tag
"show subtree" t
)
77 (const :tag
"show entry" nil
)))
79 (defcustom org-toc-recenter-mode t
80 "Non-nil means recenter the Org buffer when following the
81 headlines in the TOC buffer."
85 (defcustom org-toc-recenter
0
86 "Where to recenter the Org buffer when unfolding a subtree.
87 This variable is only used when `org-toc-recenter-mode' is set to
88 'custom. A value >=1000 will call recenter with no arg."
92 (defcustom org-toc-info-exclude
'("ALLTAGS")
93 "A list of excluded properties when displaying info in the
94 echo-area. The COLUMNS property is always exluded."
99 (defvar org-toc-mode-map
(make-sparse-keymap)
100 "Keymap for `org-toc-mode'.")
102 (defun org-toc-mode ()
103 "A major mode for browsing the table of contents of an Org buffer.
105 \\{org-toc-mode-map}"
107 (kill-all-local-variables)
108 (use-local-map org-toc-mode-map
)
109 (setq mode-name
"Org TOC")
110 (setq major-mode
'org-toc-mode
))
113 (define-key org-toc-mode-map
"f" 'org-toc-follow-mode
)
114 (define-key org-toc-mode-map
"S" 'org-toc-show-subtree-mode
)
115 (define-key org-toc-mode-map
"s" 'org-toc-store-config
)
116 (define-key org-toc-mode-map
"g" 'org-toc-restore-config
)
117 (define-key org-toc-mode-map
"i" 'org-toc-info-mode
)
118 (define-key org-toc-mode-map
"r" 'org-toc-recenter-mode
)
121 (define-key org-toc-mode-map
"p" 'org-toc-previous
)
122 (define-key org-toc-mode-map
"n" 'org-toc-next
)
123 (define-key org-toc-mode-map
[(left)] 'org-toc-previous
)
124 (define-key org-toc-mode-map
[(right)] 'org-toc-next
)
125 (define-key org-toc-mode-map
[(up)] 'org-toc-previous
)
126 (define-key org-toc-mode-map
[(down)] 'org-toc-next
)
127 (define-key org-toc-mode-map
"1" (lambda() (interactive) (org-toc-show 1 (point))))
128 (define-key org-toc-mode-map
"2" (lambda() (interactive) (org-toc-show 2 (point))))
129 (define-key org-toc-mode-map
"3" (lambda() (interactive) (org-toc-show 3 (point))))
130 (define-key org-toc-mode-map
"4" (lambda() (interactive) (org-toc-show 4 (point))))
131 (define-key org-toc-mode-map
" " 'org-toc-goto
)
132 (define-key org-toc-mode-map
"q" 'org-toc-quit
)
133 (define-key org-toc-mode-map
"x" 'org-toc-quit
)
134 ;; go to the location and stay in the base buffer
135 (define-key org-toc-mode-map
[(tab)] 'org-toc-jump
)
136 (define-key org-toc-mode-map
"v" 'org-toc-jump
)
137 ;; go to the location and delete other windows
138 (define-key org-toc-mode-map
[(return)]
139 (lambda() (interactive) (org-toc-jump t
)))
142 (define-key org-toc-mode-map
"c" 'org-toc-columns
)
143 (define-key org-toc-mode-map
"?" 'org-toc-help
)
144 (define-key org-toc-mode-map
":" 'org-toc-cycle-subtree
)
145 (define-key org-toc-mode-map
"\C-c\C-o" 'org-open-at-point
)
146 ;; global cycling in the base buffer
147 (define-key org-toc-mode-map
(kbd "C-S-<iso-lefttab>")
148 'org-toc-cycle-base-buffer
)
149 ;; subtree cycling in the base buffer
150 (define-key org-toc-mode-map
[(control tab
)]
151 (lambda() (interactive) (org-toc-goto nil t
)))
153 ;;; Toggle functions:
154 (defun org-toc-follow-mode ()
155 "Toggle follow mode in a `org-toc-mode' buffer."
157 (setq org-toc-follow-mode
(not org-toc-follow-mode
))
158 (message "Follow mode is %s"
159 (if org-toc-follow-mode
"on" "off")))
161 (defun org-toc-info-mode ()
162 "Toggle info mode in a `org-toc-mode' buffer."
164 (setq org-toc-info-mode
(not org-toc-info-mode
))
165 (message "Info mode is %s"
166 (if org-toc-info-mode
"on" "off")))
168 (defun org-toc-show-subtree-mode ()
169 "Toggle show subtree mode in a `org-toc-mode' buffer."
171 (setq org-toc-show-subtree-mode
(not org-toc-show-subtree-mode
))
172 (message "Show subtree mode is %s"
173 (if org-toc-show-subtree-mode
"on" "off")))
175 (defun org-toc-recenter-mode (&optional line
)
176 "Toggle recenter mode in a `org-toc-mode' buffer. If LINE is
177 specified, then make `org-toc-recenter' use this value."
179 (setq org-toc-recenter-mode
(not org-toc-recenter-mode
))
181 (setq org-toc-recenter-mode t
)
182 (setq org-toc-recenter line
))
183 (message "Recenter mode is %s"
184 (if org-toc-recenter-mode
185 (format "on, line %d" org-toc-recenter
) "off")))
187 (defun org-toc-cycle-subtree ()
188 "Locally cycle a headline through two states: 'children and
192 (end (save-excursion (end-of-line) (point)))
193 (ov (car (overlays-at (point))))
195 (if ov
(setq status
(overlay-get ov
'status
))
196 (setq ov
(make-overlay beg end
)))
197 ;; change the folding status of this headline
198 (cond ((or (null status
) (eq status
'folded
))
201 (overlay-put ov
'status
'children
))
202 ((eq status
'children
)
205 (overlay-put ov
'status
'branches
))
208 (overlay-put ov
'status
'folded
)))))
210 ;;; Main show function:
211 ;; FIXME name this org-before-first-heading-p?
212 (defun org-toc-before-first-heading-p ()
213 "Before first heading?"
215 (null (re-search-backward org-outline-regexp-bol nil t
))))
218 (defun org-toc-show (&optional depth position
)
219 "Show the table of contents of the current Org-mode buffer."
221 (if (eq major-mode
'org-mode
)
222 (progn (setq org-toc-base-buffer
(current-buffer))
223 (setq org-toc-odd-levels-only org-odd-levels-only
))
224 (if (eq major-mode
'org-toc-mode
)
225 (org-pop-to-buffer-same-window org-toc-base-buffer
)
226 (error "Not in an Org buffer")))
227 ;; create the new window display
228 (let ((pos (or position
230 (if (org-toc-before-first-heading-p)
231 (progn (re-search-forward org-outline-regexp-bol nil t
)
234 (setq org-toc-cycle-global-status org-cycle-global-status
)
235 (delete-other-windows)
236 (and (get-buffer "*org-toc*") (kill-buffer "*org-toc*"))
237 (switch-to-buffer-other-window
238 (make-indirect-buffer org-toc-base-buffer
"*org-toc*"))
239 ;; make content before 1st headline invisible
240 (goto-char (point-min))
241 (let* ((beg (point-min))
242 (end (and (re-search-forward "^\\*" nil t
)
243 (1- (match-beginning 0))))
244 (ov (make-overlay beg end
))
245 (help (format "Table of contents for %s (press ? for a quick help):\n"
246 (buffer-name org-toc-base-buffer
))))
247 (overlay-put ov
'invisible t
)
248 (overlay-put ov
'before-string help
))
249 ;; build the browsable TOC
251 (let* ((dpth (if org-toc-odd-levels-only
252 (1- (* depth
2)) depth
)))
254 (setq org-toc-cycle-global-status
255 `(org-content ,dpth
))))
256 ((null org-toc-default-depth
)
257 (if (eq org-toc-cycle-global-status
'overview
)
258 (progn (org-overview)
259 (setq org-cycle-global-status
'overview
)
260 (run-hook-with-args 'org-cycle-hook
'overview
))
261 (progn (org-overview)
262 ;; FIXME org-content to show only headlines?
264 (setq org-cycle-global-status
'contents
)
265 (run-hook-with-args 'org-cycle-hook
'contents
))))
266 (t (let* ((dpth0 org-toc-default-depth
)
267 (dpth (if org-toc-odd-levels-only
268 (1- (* dpth0
2)) dpth0
)))
270 (setq org-toc-cycle-global-status
271 `(org-content ,dpth
)))))
273 (move-beginning-of-line nil
)
275 (shrink-window-if-larger-than-buffer)
276 (setq buffer-read-only t
))
278 ;;; Navigation functions:
279 (defun org-toc-goto (&optional jump cycle
)
280 "From Org TOC buffer, follow the targeted subtree in the Org window.
281 If JUMP is non-nil, go to the base buffer.
282 If JUMP is 'delete, go to the base buffer and delete other windows.
283 If CYCLE is non-nil, cycle the targeted subtree in the Org window."
286 (toc-buf (current-buffer)))
287 (switch-to-buffer-other-window org-toc-base-buffer
)
289 (if cycle
(org-cycle)
290 (progn (org-overview)
291 (if org-toc-show-subtree-mode
295 (if org-toc-recenter-mode
296 (if (>= org-toc-recenter
1000) (recenter)
297 (recenter org-toc-recenter
)))
299 (switch-to-buffer-other-window toc-buf
))
301 (delete-other-windows)))))
303 (defun org-toc-cycle-base-buffer ()
304 "Call `org-cycle' with a prefix argument in the base buffer."
306 (switch-to-buffer-other-window org-toc-base-buffer
)
310 (defun org-toc-jump (&optional delete
)
311 "From Org TOC buffer, jump to the targeted subtree in the Org window.
312 If DELETE is non-nil, delete other windows when in the Org buffer."
314 (if delete
(org-toc-goto 'delete
)
317 (defun org-toc-previous ()
318 "Go to the previous headline of the TOC."
322 (re-search-backward "^\\*" nil t
))
323 (outline-previous-visible-heading 1)
324 (message "No previous heading"))
325 (if org-toc-info-mode
(org-toc-info))
326 (if org-toc-follow-mode
(org-toc-goto)))
328 (defun org-toc-next ()
329 "Go to the next headline of the TOC."
331 (outline-next-visible-heading 1)
332 (if org-toc-info-mode
(org-toc-info))
333 (if org-toc-follow-mode
(org-toc-goto)))
335 (defun org-toc-quit ()
336 "Quit the current Org TOC buffer."
340 (delete-other-windows))
342 ;;; Special functions:
343 (defun org-toc-columns ()
344 "Toggle columns view in the Org buffer from Org TOC."
346 (let ((indirect-buffer (current-buffer)))
347 (org-pop-to-buffer-same-window org-toc-base-buffer
)
348 (if (not org-toc-columns-shown
)
350 (setq org-toc-columns-shown t
))
351 (progn (org-columns-remove-overlays)
352 (setq org-toc-columns-shown nil
)))
353 (org-pop-to-buffer-same-window indirect-buffer
)))
355 (defun org-toc-info ()
356 "Show properties of current subtree in the echo-area."
359 (indirect-buffer (current-buffer))
361 (org-pop-to-buffer-same-window org-toc-base-buffer
)
363 (setq props
(org-entry-properties))
364 (while (setq prop
(pop props
))
365 (unless (or (equal (car prop
) "COLUMNS")
366 (member (car prop
) org-toc-info-exclude
))
370 (setq v
(mapconcat 'identity
(split-string v
":" t
) " ")))
371 (setq p
(concat p
":"))
372 (add-text-properties 0 (length p
) '(face org-special-keyword
) p
)
373 (setq msg
(concat msg p
" " v
" ")))))
374 (org-pop-to-buffer-same-window indirect-buffer
)
377 ;;; Store and restore TOC configuration:
378 (defun org-toc-store-config ()
379 "Store the current status of the tables of contents in
380 `org-toc-config-alist'."
382 (let ((file (buffer-file-name org-toc-base-buffer
))
384 (hlcfg (org-toc-get-headlines-status)))
385 (setq org-toc-config-alist
386 (delete (assoc file org-toc-config-alist
)
387 org-toc-config-alist
))
388 (add-to-list 'org-toc-config-alist
389 `(,file
,pos
,org-toc-cycle-global-status
,hlcfg
))
390 (message "TOC configuration saved: (%s)"
391 (if (listp org-toc-cycle-global-status
)
392 (concat "org-content "
394 (cadr org-toc-cycle-global-status
)))
395 (symbol-name org-toc-cycle-global-status
)))))
397 (defun org-toc-restore-config ()
398 "Get the stored status in `org-toc-config-alist' and set the
399 current table of contents to it."
401 (let* ((file (buffer-file-name org-toc-base-buffer
))
402 (conf (cdr (assoc file org-toc-config-alist
)))
405 (hlcfg (caddr conf
)) hlcfg0 ov
)
406 (cond ((listp status
)
407 (org-toc-show (cadr status
) (point)))
408 ((eq status
'overview
)
410 (setq org-cycle-global-status
'overview
)
411 (run-hook-with-args 'org-cycle-hook
'overview
))
415 (setq org-cycle-global-status
'contents
)
416 (run-hook-with-args 'org-cycle-hook
'contents
)))
417 (while (setq hlcfg0
(pop hlcfg
))
419 (goto-char (point-min))
420 (when (search-forward (car hlcfg0
) nil t
)
421 (unless (overlays-at (match-beginning 0))
422 (setq ov
(make-overlay (match-beginning 0)
424 (cond ((eq (cdr hlcfg0
) 'children
)
427 (overlay-put ov
'status
'children
))
428 ((eq (cdr hlcfg0
) 'branches
)
431 (overlay-put ov
'status
'branches
))))))
433 (if org-toc-follow-mode
(org-toc-goto))
434 (message "Last TOC configuration restored")
436 (if org-toc-info-mode
(org-toc-info))))
438 (defun org-toc-get-headlines-status ()
439 "Return an alist of headlines and their associated folding
443 (goto-char (point-min))
444 (while (and (not (eobp))
445 (goto-char (next-overlay-change (point))))
446 (when (looking-at org-outline-regexp-bol
)
449 (cons (buffer-substring-no-properties
452 (end-of-line) (point)))
454 (car (overlays-at (point))) 'status
))))))
455 ;; return an alist like (("* Headline" . 'status))
458 ;; In Org TOC buffer, hide headlines below the first level.
459 (defun org-toc-help ()
460 "Display a quick help message in the echo-area for `org-toc-mode'."
464 "\[space\] show heading \[1-4\] hide headlines below this level
465 \[TAB\] jump to heading \[f\] toggle follow mode (currently %s)
466 \[return\] jump and delete others windows \[i\] toggle info mode (currently %s)
467 \[S-TAB\] cycle subtree (in Org) \[S\] toggle show subtree mode (currently %s)
468 \[C-S-TAB\] global cycle (in Org) \[r\] toggle recenter mode (currently %s)
469 \[:\] cycle subtree (in TOC) \[c\] toggle column view (currently %s)
470 \[n/p\] next/previous heading \[s\] save TOC configuration
471 \[q\] quit the TOC \[g\] restore last TOC configuration"))
472 (while (string-match "\\[[^]]+\\]" help-message st-start
)
473 (add-text-properties (match-beginning 0)
474 (match-end 0) '(face bold
) help-message
)
475 (setq st-start
(match-end 0)))
476 (message help-message
477 (if org-toc-follow-mode
"on" "off")
478 (if org-toc-info-mode
"on" "off")
479 (if org-toc-show-subtree-mode
"on" "off")
480 (if org-toc-recenter-mode
(format "on, line %s" org-toc-recenter
) "off")
481 (if org-toc-columns-shown
"on" "off"))))
484 ;;;;##########################################################################
485 ;;;; User Options, Variables
486 ;;;;##########################################################################
488 ;;; org-toc.el ends here