org-toc.el 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. ;;; org-toc.el --- Table of contents for Org-mode buffer
  2. ;; Copyright 2007-2013 Free Software Foundation, Inc.
  3. ;;
  4. ;; Author: Bastien Guerry <bzg AT gnu DOT org>
  5. ;; Keywords: Org table of contents
  6. ;; Homepage: http://www.cognition.ens.fr/~guerry/u/org-toc.el
  7. ;; Version: 0.8
  8. ;; This file is not part of GNU Emacs.
  9. ;; This program is free software; you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation; either version 3, or (at your option)
  12. ;; any later version.
  13. ;;
  14. ;; This program is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;; GNU General Public License for more details.
  18. ;;
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with this program; if not, write to the Free Software
  21. ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22. ;;; Commentary:
  23. ;; This library implements a browsable table of contents for Org files.
  24. ;; Put this file into your load-path and the following into your ~/.emacs:
  25. ;; (require 'org-toc)
  26. ;;; Code:
  27. (provide 'org-toc)
  28. (eval-when-compile
  29. (require 'cl))
  30. ;;; Custom variables:
  31. (defvar org-toc-base-buffer nil)
  32. (defvar org-toc-columns-shown nil)
  33. (defvar org-toc-odd-levels-only nil)
  34. (defvar org-toc-config-alist nil)
  35. (defvar org-toc-cycle-global-status nil)
  36. (defalias 'org-show-table-of-contents 'org-toc-show)
  37. (defgroup org-toc nil
  38. "Options concerning the browsable table of contents of Org-mode."
  39. :tag "Org TOC"
  40. :group 'org)
  41. (defcustom org-toc-default-depth 1
  42. "Default depth when invoking `org-toc-show' without argument."
  43. :group 'org-toc
  44. :type '(choice
  45. (const :tag "same as base buffer" nil)
  46. (integer :tag "level")))
  47. (defcustom org-toc-follow-mode nil
  48. "Non-nil means navigating through the table of contents will
  49. move the point in the Org buffer accordingly."
  50. :group 'org-toc
  51. :type 'boolean)
  52. (defcustom org-toc-info-mode nil
  53. "Non-nil means navigating through the table of contents will
  54. show the properties for the current headline in the echo-area."
  55. :group 'org-toc
  56. :type 'boolean)
  57. (defcustom org-toc-show-subtree-mode nil
  58. "Non-nil means show subtree when going to headline or following
  59. it while browsing the table of contents."
  60. :group 'org-toc
  61. :type '(choice
  62. (const :tag "show subtree" t)
  63. (const :tag "show entry" nil)))
  64. (defcustom org-toc-recenter-mode t
  65. "Non-nil means recenter the Org buffer when following the
  66. headlines in the TOC buffer."
  67. :group 'org-toc
  68. :type 'boolean)
  69. (defcustom org-toc-recenter 0
  70. "Where to recenter the Org buffer when unfolding a subtree.
  71. This variable is only used when `org-toc-recenter-mode' is set to
  72. 'custom. A value >=1000 will call recenter with no arg."
  73. :group 'org-toc
  74. :type 'integer)
  75. (defcustom org-toc-info-exclude '("ALLTAGS")
  76. "A list of excluded properties when displaying info in the
  77. echo-area. The COLUMNS property is always exluded."
  78. :group 'org-toc
  79. :type 'lits)
  80. ;;; Org TOC mode:
  81. (defvar org-toc-mode-map (make-sparse-keymap)
  82. "Keymap for `org-toc-mode'.")
  83. (defun org-toc-mode ()
  84. "A major mode for browsing the table of contents of an Org buffer.
  85. \\{org-toc-mode-map}"
  86. (interactive)
  87. (kill-all-local-variables)
  88. (use-local-map org-toc-mode-map)
  89. (setq mode-name "Org TOC")
  90. (setq major-mode 'org-toc-mode))
  91. ;; toggle modes
  92. (define-key org-toc-mode-map "F" 'org-toc-follow-mode)
  93. (define-key org-toc-mode-map "S" 'org-toc-show-subtree-mode)
  94. (define-key org-toc-mode-map "s" 'org-toc-store-config)
  95. (define-key org-toc-mode-map "g" 'org-toc-restore-config)
  96. (define-key org-toc-mode-map "i" 'org-toc-info-mode)
  97. (define-key org-toc-mode-map "r" 'org-toc-recenter-mode)
  98. ;; navigation keys
  99. (define-key org-toc-mode-map "p" 'org-toc-previous)
  100. (define-key org-toc-mode-map "n" 'org-toc-next)
  101. (define-key org-toc-mode-map "f" 'org-toc-forward)
  102. (define-key org-toc-mode-map "b" 'org-toc-back)
  103. (define-key org-toc-mode-map [(left)] 'org-toc-back)
  104. (define-key org-toc-mode-map [(right)] 'org-toc-forward)
  105. (define-key org-toc-mode-map [(up)] 'org-toc-previous)
  106. (define-key org-toc-mode-map [(down)] 'org-toc-next)
  107. (define-key org-toc-mode-map "1" (lambda() (interactive) (org-toc-show 1 (point))))
  108. (define-key org-toc-mode-map "2" (lambda() (interactive) (org-toc-show 2 (point))))
  109. (define-key org-toc-mode-map "3" (lambda() (interactive) (org-toc-show 3 (point))))
  110. (define-key org-toc-mode-map "4" (lambda() (interactive) (org-toc-show 4 (point))))
  111. (define-key org-toc-mode-map " " 'org-toc-goto)
  112. (define-key org-toc-mode-map "q" 'org-toc-quit)
  113. (define-key org-toc-mode-map "x" 'org-toc-quit)
  114. ;; go to the location and stay in the base buffer
  115. (define-key org-toc-mode-map [(tab)] 'org-toc-jump)
  116. (define-key org-toc-mode-map "v" 'org-toc-jump)
  117. ;; go to the location and delete other windows
  118. (define-key org-toc-mode-map [(return)]
  119. (lambda() (interactive) (org-toc-jump t)))
  120. ;; special keys
  121. (define-key org-toc-mode-map "c" 'org-toc-columns)
  122. (define-key org-toc-mode-map "?" 'org-toc-help)
  123. (define-key org-toc-mode-map ":" 'org-toc-cycle-subtree)
  124. (define-key org-toc-mode-map "\C-c\C-o" 'org-open-at-point)
  125. ;; global cycling in the base buffer
  126. (define-key org-toc-mode-map (kbd "C-S-<iso-lefttab>")
  127. 'org-toc-cycle-base-buffer)
  128. ;; subtree cycling in the base buffer
  129. (define-key org-toc-mode-map [(control tab)]
  130. (lambda() (interactive) (org-toc-goto nil t)))
  131. ;;; Toggle functions:
  132. (defun org-toc-follow-mode ()
  133. "Toggle follow mode in a `org-toc-mode' buffer."
  134. (interactive)
  135. (setq org-toc-follow-mode (not org-toc-follow-mode))
  136. (message "Follow mode is %s"
  137. (if org-toc-follow-mode "on" "off")))
  138. (defun org-toc-info-mode ()
  139. "Toggle info mode in a `org-toc-mode' buffer."
  140. (interactive)
  141. (setq org-toc-info-mode (not org-toc-info-mode))
  142. (message "Info mode is %s"
  143. (if org-toc-info-mode "on" "off")))
  144. (defun org-toc-show-subtree-mode ()
  145. "Toggle show subtree mode in a `org-toc-mode' buffer."
  146. (interactive)
  147. (setq org-toc-show-subtree-mode (not org-toc-show-subtree-mode))
  148. (message "Show subtree mode is %s"
  149. (if org-toc-show-subtree-mode "on" "off")))
  150. (defun org-toc-recenter-mode (&optional line)
  151. "Toggle recenter mode in a `org-toc-mode' buffer. If LINE is
  152. specified, then make `org-toc-recenter' use this value."
  153. (interactive "P")
  154. (setq org-toc-recenter-mode (not org-toc-recenter-mode))
  155. (when (numberp line)
  156. (setq org-toc-recenter-mode t)
  157. (setq org-toc-recenter line))
  158. (message "Recenter mode is %s"
  159. (if org-toc-recenter-mode
  160. (format "on, line %d" org-toc-recenter) "off")))
  161. (defun org-toc-cycle-subtree ()
  162. "Locally cycle a headline through two states: 'children and
  163. 'folded"
  164. (interactive)
  165. (let ((beg (point))
  166. (end (save-excursion (end-of-line) (point)))
  167. (ov (car (overlays-at (point))))
  168. status)
  169. (if ov (setq status (overlay-get ov 'status))
  170. (setq ov (make-overlay beg end)))
  171. ;; change the folding status of this headline
  172. (cond ((or (null status) (eq status 'folded))
  173. (show-children)
  174. (message "CHILDREN")
  175. (overlay-put ov 'status 'children))
  176. ((eq status 'children)
  177. (show-branches)
  178. (message "BRANCHES")
  179. (overlay-put ov 'status 'branches))
  180. (t (hide-subtree)
  181. (message "FOLDED")
  182. (overlay-put ov 'status 'folded)))))
  183. ;;; Main show function:
  184. ;; FIXME name this org-before-first-heading-p?
  185. (defun org-toc-before-first-heading-p ()
  186. "Before first heading?"
  187. (save-excursion
  188. (null (re-search-backward org-outline-regexp-bol nil t))))
  189. ;;;###autoload
  190. (defun org-toc-show (&optional depth position)
  191. "Show the table of contents of the current Org-mode buffer."
  192. (interactive "P")
  193. (if (eq major-mode 'org-mode)
  194. (progn (setq org-toc-base-buffer (current-buffer))
  195. (setq org-toc-odd-levels-only org-odd-levels-only))
  196. (if (eq major-mode 'org-toc-mode)
  197. (org-pop-to-buffer-same-window org-toc-base-buffer)
  198. (error "Not in an Org buffer")))
  199. ;; create the new window display
  200. (let ((pos (or position
  201. (save-excursion
  202. (if (org-toc-before-first-heading-p)
  203. (progn (re-search-forward org-outline-regexp-bol nil t)
  204. (match-beginning 0))
  205. (point))))))
  206. (setq org-toc-cycle-global-status org-cycle-global-status)
  207. (delete-other-windows)
  208. (and (get-buffer "*org-toc*") (kill-buffer "*org-toc*"))
  209. (switch-to-buffer-other-window
  210. (make-indirect-buffer org-toc-base-buffer "*org-toc*"))
  211. ;; make content before 1st headline invisible
  212. (goto-char (point-min))
  213. (let* ((beg (point-min))
  214. (end (and (re-search-forward "^\\*" nil t)
  215. (1- (match-beginning 0))))
  216. (ov (make-overlay beg end))
  217. (help (format "Table of contents for %s (press ? for a quick help):\n"
  218. (buffer-name org-toc-base-buffer))))
  219. (overlay-put ov 'invisible t)
  220. (overlay-put ov 'before-string help))
  221. ;; build the browsable TOC
  222. (cond (depth
  223. (let* ((dpth (if org-toc-odd-levels-only
  224. (1- (* depth 2)) depth)))
  225. (org-content dpth)
  226. (setq org-toc-cycle-global-status
  227. `(org-content ,dpth))))
  228. ((null org-toc-default-depth)
  229. (if (eq org-toc-cycle-global-status 'overview)
  230. (progn (org-overview)
  231. (setq org-cycle-global-status 'overview)
  232. (run-hook-with-args 'org-cycle-hook 'overview))
  233. (progn (org-overview)
  234. ;; FIXME org-content to show only headlines?
  235. (org-content)
  236. (setq org-cycle-global-status 'contents)
  237. (run-hook-with-args 'org-cycle-hook 'contents))))
  238. (t (let* ((dpth0 org-toc-default-depth)
  239. (dpth (if org-toc-odd-levels-only
  240. (1- (* dpth0 2)) dpth0)))
  241. (org-content dpth)
  242. (setq org-toc-cycle-global-status
  243. `(org-content ,dpth)))))
  244. (goto-char pos))
  245. (move-beginning-of-line nil)
  246. (org-toc-mode)
  247. (shrink-window-if-larger-than-buffer)
  248. (setq buffer-read-only t))
  249. ;;; Navigation functions:
  250. (defun org-toc-goto (&optional jump cycle)
  251. "From Org TOC buffer, follow the targeted subtree in the Org window.
  252. If JUMP is non-nil, go to the base buffer.
  253. If JUMP is 'delete, go to the base buffer and delete other windows.
  254. If CYCLE is non-nil, cycle the targeted subtree in the Org window."
  255. (interactive)
  256. (let ((pos (point))
  257. (toc-buf (current-buffer)))
  258. (switch-to-buffer-other-window org-toc-base-buffer)
  259. (goto-char pos)
  260. (if cycle (org-cycle)
  261. (progn (org-overview)
  262. (if org-toc-show-subtree-mode
  263. (org-show-subtree)
  264. (org-show-entry))
  265. (org-show-context)))
  266. (if org-toc-recenter-mode
  267. (if (>= org-toc-recenter 1000) (recenter)
  268. (recenter org-toc-recenter)))
  269. (cond ((null jump)
  270. (switch-to-buffer-other-window toc-buf))
  271. ((eq jump 'delete)
  272. (delete-other-windows)))))
  273. (defun org-toc-cycle-base-buffer ()
  274. "Call `org-cycle' with a prefix argument in the base buffer."
  275. (interactive)
  276. (switch-to-buffer-other-window org-toc-base-buffer)
  277. (org-cycle t)
  278. (other-window 1))
  279. (defun org-toc-jump (&optional delete)
  280. "From Org TOC buffer, jump to the targeted subtree in the Org window.
  281. If DELETE is non-nil, delete other windows when in the Org buffer."
  282. (interactive "P")
  283. (if delete (org-toc-goto 'delete)
  284. (org-toc-goto t)))
  285. (defun org-toc-previous ()
  286. "Go to the previous headline of the TOC."
  287. (interactive)
  288. (if (save-excursion
  289. (beginning-of-line)
  290. (re-search-backward "^\\*" nil t))
  291. (outline-previous-visible-heading 1)
  292. (message "No previous heading"))
  293. (if org-toc-info-mode (org-toc-info))
  294. (if org-toc-follow-mode (org-toc-goto)))
  295. (defun org-toc-next ()
  296. "Go to the next headline of the TOC."
  297. (interactive)
  298. (outline-next-visible-heading 1)
  299. (if org-toc-info-mode (org-toc-info))
  300. (if org-toc-follow-mode (org-toc-goto)))
  301. (defun org-toc-forward ()
  302. "Go to the next headline at the same level in the TOC."
  303. (interactive)
  304. (condition-case nil
  305. (outline-forward-same-level 1)
  306. (error (message "No next headline at this level.")))
  307. (if org-toc-info-mode (org-toc-info))
  308. (if org-toc-follow-mode (org-toc-goto)))
  309. (defun org-toc-back ()
  310. "Go to the previous headline at the same level in the TOC."
  311. (interactive)
  312. (condition-case nil
  313. (outline-backward-same-level 1)
  314. (error (message "No previous headline at this level.")))
  315. (if org-toc-info-mode (org-toc-info))
  316. (if org-toc-follow-mode (org-toc-goto)))
  317. (defun org-toc-quit ()
  318. "Quit the current Org TOC buffer."
  319. (interactive)
  320. (kill-this-buffer)
  321. (other-window 1)
  322. (delete-other-windows))
  323. ;;; Special functions:
  324. (defun org-toc-columns ()
  325. "Toggle columns view in the Org buffer from Org TOC."
  326. (interactive)
  327. (let ((indirect-buffer (current-buffer)))
  328. (org-pop-to-buffer-same-window org-toc-base-buffer)
  329. (if (not org-toc-columns-shown)
  330. (progn (org-columns)
  331. (setq org-toc-columns-shown t))
  332. (progn (org-columns-remove-overlays)
  333. (setq org-toc-columns-shown nil)))
  334. (org-pop-to-buffer-same-window indirect-buffer)))
  335. (defun org-toc-info ()
  336. "Show properties of current subtree in the echo-area."
  337. (interactive)
  338. (let ((pos (point))
  339. (indirect-buffer (current-buffer))
  340. props prop msg)
  341. (org-pop-to-buffer-same-window org-toc-base-buffer)
  342. (goto-char pos)
  343. (setq props (org-entry-properties))
  344. (while (setq prop (pop props))
  345. (unless (or (equal (car prop) "COLUMNS")
  346. (member (car prop) org-toc-info-exclude))
  347. (let ((p (car prop))
  348. (v (cdr prop)))
  349. (if (equal p "TAGS")
  350. (setq v (mapconcat 'identity (split-string v ":" t) " ")))
  351. (setq p (concat p ":"))
  352. (add-text-properties 0 (length p) '(face org-special-keyword) p)
  353. (setq msg (concat msg p " " v " ")))))
  354. (org-pop-to-buffer-same-window indirect-buffer)
  355. (message msg)))
  356. ;;; Store and restore TOC configuration:
  357. (defun org-toc-store-config ()
  358. "Store the current status of the tables of contents in
  359. `org-toc-config-alist'."
  360. (interactive)
  361. (let ((file (buffer-file-name org-toc-base-buffer))
  362. (pos (point))
  363. (hlcfg (org-toc-get-headlines-status)))
  364. (setq org-toc-config-alist
  365. (delete (assoc file org-toc-config-alist)
  366. org-toc-config-alist))
  367. (add-to-list 'org-toc-config-alist
  368. `(,file ,pos ,org-toc-cycle-global-status ,hlcfg))
  369. (message "TOC configuration saved: (%s)"
  370. (if (listp org-toc-cycle-global-status)
  371. (concat "org-content "
  372. (number-to-string
  373. (cadr org-toc-cycle-global-status)))
  374. (symbol-name org-toc-cycle-global-status)))))
  375. (defun org-toc-restore-config ()
  376. "Get the stored status in `org-toc-config-alist' and set the
  377. current table of contents to it."
  378. (interactive)
  379. (let* ((file (buffer-file-name org-toc-base-buffer))
  380. (conf (cdr (assoc file org-toc-config-alist)))
  381. (pos (car conf))
  382. (status (cadr conf))
  383. (hlcfg (caddr conf)) hlcfg0 ov)
  384. (cond ((listp status)
  385. (org-toc-show (cadr status) (point)))
  386. ((eq status 'overview)
  387. (org-overview)
  388. (setq org-cycle-global-status 'overview)
  389. (run-hook-with-args 'org-cycle-hook 'overview))
  390. (t
  391. (org-overview)
  392. (org-content)
  393. (setq org-cycle-global-status 'contents)
  394. (run-hook-with-args 'org-cycle-hook 'contents)))
  395. (while (setq hlcfg0 (pop hlcfg))
  396. (save-excursion
  397. (goto-char (point-min))
  398. (when (search-forward (car hlcfg0) nil t)
  399. (unless (overlays-at (match-beginning 0))
  400. (setq ov (make-overlay (match-beginning 0)
  401. (match-end 0))))
  402. (cond ((eq (cdr hlcfg0) 'children)
  403. (show-children)
  404. (message "CHILDREN")
  405. (overlay-put ov 'status 'children))
  406. ((eq (cdr hlcfg0) 'branches)
  407. (show-branches)
  408. (message "BRANCHES")
  409. (overlay-put ov 'status 'branches))))))
  410. (goto-char pos)
  411. (if org-toc-follow-mode (org-toc-goto))
  412. (message "Last TOC configuration restored")
  413. (sit-for 1)
  414. (if org-toc-info-mode (org-toc-info))))
  415. (defun org-toc-get-headlines-status ()
  416. "Return an alist of headlines and their associated folding
  417. status."
  418. (let (output ovs)
  419. (save-excursion
  420. (goto-char (point-min))
  421. (while (and (not (eobp))
  422. (goto-char (next-overlay-change (point))))
  423. (when (looking-at org-outline-regexp-bol)
  424. (add-to-list
  425. 'output
  426. (cons (buffer-substring-no-properties
  427. (match-beginning 0)
  428. (save-excursion
  429. (end-of-line) (point)))
  430. (overlay-get
  431. (car (overlays-at (point))) 'status))))))
  432. ;; return an alist like (("* Headline" . 'status))
  433. output))
  434. ;; In Org TOC buffer, hide headlines below the first level.
  435. (defun org-toc-help ()
  436. "Display a quick help message in the echo-area for `org-toc-mode'."
  437. (interactive)
  438. (let ((st-start 0)
  439. (help-message
  440. "\[space\] show heading \[1-4\] hide headlines below this level
  441. \[TAB\] jump to heading \[F\] toggle follow mode (currently %s)
  442. \[return\] jump and delete others windows \[i\] toggle info mode (currently %s)
  443. \[S-TAB\] cycle subtree (in Org) \[S\] toggle show subtree mode (currently %s)
  444. \[C-S-TAB\] global cycle (in Org) \[r\] toggle recenter mode (currently %s)
  445. \[:\] cycle subtree (in TOC) \[c\] toggle column view (currently %s)
  446. \[n/p\] next/previous heading \[s\] save TOC configuration
  447. \[f/b\] next/previous heading of same level
  448. \[q\] quit the TOC \[g\] restore last TOC configuration"))
  449. (while (string-match "\\[[^]]+\\]" help-message st-start)
  450. (add-text-properties (match-beginning 0)
  451. (match-end 0) '(face bold) help-message)
  452. (setq st-start (match-end 0)))
  453. (message help-message
  454. (if org-toc-follow-mode "on" "off")
  455. (if org-toc-info-mode "on" "off")
  456. (if org-toc-show-subtree-mode "on" "off")
  457. (if org-toc-recenter-mode (format "on, line %s" org-toc-recenter) "off")
  458. (if org-toc-columns-shown "on" "off"))))
  459. ;;;;##########################################################################
  460. ;;;; User Options, Variables
  461. ;;;;##########################################################################
  462. ;;; org-toc.el ends here