213 lines
7.6 KiB
EmacsLisp
213 lines
7.6 KiB
EmacsLisp
|
;;; org-indent.el --- Dynamic indentation for Org-mode
|
||
|
;; Copyright (C) 2008 Free Software Foundation, Inc.
|
||
|
;;
|
||
|
;; Author: Carsten Dominik <carsten at orgmode dot org>
|
||
|
;; Keywords: outlines, hypermedia, calendar, wp
|
||
|
;; Homepage: http://orgmode.org
|
||
|
;; Version: 0.07
|
||
|
;;
|
||
|
;; This file is not part of GNU Emacs.
|
||
|
;;
|
||
|
;; This file is free software; you can redistribute it and/or modify
|
||
|
;; it under the terms of the GNU General Public License as published by
|
||
|
;; the Free Software Foundation; either version 3, or (at your option)
|
||
|
;; any later version.
|
||
|
|
||
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
;; GNU General Public License for more details.
|
||
|
|
||
|
;; You should have received a copy of the GNU General Public License
|
||
|
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||
|
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
|
;; Boston, MA 02110-1301, USA.
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;;
|
||
|
;;; Commentary:
|
||
|
|
||
|
;; This is an experimental implementation of dynamic virtual indentation.
|
||
|
;; It works by adding overlays to a buffer to make sure lines are
|
||
|
;; indented according to outline structure. While this works, there are
|
||
|
;; problems with the implementation: It uses overlays, which use markers,
|
||
|
;; and for large files, this is using too much resources. It might be
|
||
|
;; possible to com up with an implementation using text properties,
|
||
|
;; I believe this is less resource intensive. However, it does not work
|
||
|
;; to put the text property onto the newline, because that interferes with
|
||
|
;; outline visibility. Maybe this is a bug in outline?
|
||
|
|
||
|
;;; Indentation
|
||
|
|
||
|
(defconst org-indent-max 80
|
||
|
"Maximum indentation in characters")
|
||
|
(defconst org-indent-strings nil
|
||
|
"Vector with all indentation strings.
|
||
|
It is a const because it will be set only once in `org-indent-initialize'.")
|
||
|
(defvar org-indent-inhibit-after-change nil
|
||
|
"Inhibit the action of the indentation after-change-hook.
|
||
|
This variable should be scoped dynamically.")
|
||
|
(defcustom org-indent-boundary-char ?\ ; comment to protect space char
|
||
|
"The end of the virtual indentation strings, a single-character string.
|
||
|
The default is just a space, but if you wish, you can use \"|\" or so."
|
||
|
:group 'org-structure
|
||
|
:set (lambda (var val)
|
||
|
(set var val)
|
||
|
(and org-indent-strings (org-indent-initialize)))
|
||
|
:type 'character)
|
||
|
|
||
|
(defun org-indent-initialize ()
|
||
|
"Initialize the indentation strings and set the idle times."
|
||
|
(unless org-indent-strings
|
||
|
; (run-with-idle-timer 10 t 'org-indent-indent-buffer)
|
||
|
(run-with-idle-timer 0.5 t 'org-indent-refresh-section)
|
||
|
)
|
||
|
(setq org-indent-strings (make-vector (1+ org-indent-max) nil))
|
||
|
;; Initialize the indentation strings
|
||
|
(aset org-indent-strings 0 "")
|
||
|
(loop for i from 1 to org-indent-max do
|
||
|
(aset org-indent-strings i
|
||
|
(org-add-props
|
||
|
(concat (make-string (1- i) ?\ )
|
||
|
(char-to-string org-indent-boundary-char))
|
||
|
nil 'face 'org-indent))))
|
||
|
|
||
|
(define-minor-mode org-indent-mode
|
||
|
"Toggle the minor more `org-indent-mode'."
|
||
|
nil " Ind" nil
|
||
|
(if (org-bound-and-true-p org-inhibit-startup)
|
||
|
(setq org-indent-mode nil)
|
||
|
(if org-indent-mode
|
||
|
(progn
|
||
|
(or org-indent-strings (org-indent-initialize))
|
||
|
(org-set-local 'org-adapt-indentation nil)
|
||
|
(org-add-hook 'after-change-functions
|
||
|
'org-indent-after-change-function nil t)
|
||
|
(org-restart-font-lock)
|
||
|
(org-indent-indent-buffer))
|
||
|
(save-excursion
|
||
|
(save-restriction
|
||
|
(org-indent-remove-overlays (point-min) (point-max))
|
||
|
(remove-hook 'after-change-functions
|
||
|
'org-indent-after-change-function 'local)
|
||
|
(kill-local-variable 'org-adapt-indentation))))))
|
||
|
|
||
|
(defface org-indent
|
||
|
(org-compatible-face nil
|
||
|
'((((class color) (min-colors 16) (background dark)
|
||
|
(:underline nil :strike-through nil :foreground "grey10")))
|
||
|
(((class color) (min-colors 16) (background light))
|
||
|
(:underline nil :strike-through nil :foreground "grey90"))
|
||
|
(t (:underline nil :strike-through nil))))
|
||
|
"Face for outline indentation.
|
||
|
The default is to make it look like whitespace. But you may find it
|
||
|
useful to make it, for example, look like the fringes."
|
||
|
:group 'org-faces)
|
||
|
|
||
|
(defun org-indent-indent-buffer ()
|
||
|
"Add indentation overlays for the whole buffer."
|
||
|
(interactive)
|
||
|
(when org-indent-mode
|
||
|
(save-excursion
|
||
|
(save-restriction
|
||
|
(org-indent-remove-overlays (point-min) (point-max))
|
||
|
(org-indent-add-overlays (point-min) (point-max))))))
|
||
|
|
||
|
(defun org-indent-remove-overlays (beg end)
|
||
|
"Remove indentations between BEG and END."
|
||
|
(mapc (lambda (o)
|
||
|
(and (org-overlay-get o 'org-indent)
|
||
|
(org-delete-overlay o)))
|
||
|
(org-overlays-in beg end)))
|
||
|
|
||
|
(defun org-indent-add-overlays (beg end &optional n)
|
||
|
"Add indentation overlays between BEG and END.
|
||
|
Assumes that BEG is at the beginning of a line."
|
||
|
(when org-indent-mode
|
||
|
(let (o)
|
||
|
(save-excursion
|
||
|
(goto-char beg)
|
||
|
(while (and (<= (point) end) (not (eobp)))
|
||
|
(cond
|
||
|
((not (bolp)))
|
||
|
((looking-at outline-regexp)
|
||
|
(setq n (- (match-end 0) (match-beginning 0)))
|
||
|
(org-indent-remove-overlays (max (point-min) (1- (point))) (point)))
|
||
|
(n
|
||
|
(org-indent-indent-line n)))
|
||
|
(beginning-of-line 2))))))
|
||
|
|
||
|
(defun org-indent-indent-line (n)
|
||
|
"Add an indentation overlay with width N to the current line.
|
||
|
Point is assumed to be at the beginning of the line for this."
|
||
|
(let (ov)
|
||
|
(setq ov (org-make-overlay (1- (point)) (point)))
|
||
|
(org-overlay-put ov 'after-string (aref org-indent-strings n))
|
||
|
(org-overlay-put ov 'evaporate t)
|
||
|
(org-overlay-put ov 'org-indent n)
|
||
|
(org-unmodified
|
||
|
(put-text-property (max (point-min) (1- (point)))
|
||
|
(point-at-eol) 'org-indent-level n))))
|
||
|
|
||
|
(defun org-indent-after-change-function (beg end ndel)
|
||
|
(if (or (not org-indent-mode) (= beg end)
|
||
|
org-indent-inhibit-after-change)
|
||
|
() ; not in the mood to do anything here....
|
||
|
(let ((inhibit-quit t) n)
|
||
|
(save-match-data
|
||
|
(save-excursion
|
||
|
(save-restriction
|
||
|
(widen)
|
||
|
(goto-char beg)
|
||
|
(when (search-forward "\n" end t)
|
||
|
;; a newline was inserted
|
||
|
(setq n (or (get-text-property beg 'org-indent-level)
|
||
|
(get-text-property
|
||
|
(or (save-excursion (previous-single-property-change
|
||
|
beg 'org-indent-level))
|
||
|
(point-min))
|
||
|
'org-indent-level)
|
||
|
0))
|
||
|
(org-indent-local-refresh beg end n))))))))
|
||
|
|
||
|
(defun org-indent-local-refresh (beg end n)
|
||
|
"Refresh indentation locally from BEG to END, starting with indentation N."
|
||
|
(goto-char end)
|
||
|
(setq end (min (point-max) (1+ (point-at-eol))))
|
||
|
(goto-char beg)
|
||
|
(beginning-of-line 0)
|
||
|
(org-indent-remove-overlays (max (point-min) (1- (point))) end)
|
||
|
(org-indent-add-overlays (point) end n))
|
||
|
|
||
|
(defun org-indent-refresh-section ()
|
||
|
"Refresh indentation overlays in the current outline subtree."
|
||
|
(when org-indent-mode
|
||
|
(save-excursion
|
||
|
(let ((org-indent-inhibit-after-change t)
|
||
|
beg end)
|
||
|
(condition-case nil
|
||
|
(progn
|
||
|
(outline-back-to-heading t)
|
||
|
(setq beg (point)))
|
||
|
(error (progn
|
||
|
(goto-char (point-min))
|
||
|
(setq beg (point)))))
|
||
|
(outline-next-heading)
|
||
|
(setq end (point))
|
||
|
(org-indent-remove-overlays beg end)
|
||
|
(org-indent-add-overlays beg end)))))
|
||
|
|
||
|
(defun org-indent-refresh-subtree ()
|
||
|
"Refresh indentation overlays in the current outline subtree."
|
||
|
(when org-indent-mode
|
||
|
(save-excursion
|
||
|
(let ((org-indent-inhibit-after-change t)
|
||
|
beg end)
|
||
|
(setq beg (point))
|
||
|
(setq end (save-excursion (org-end-of-subtree t t)))
|
||
|
(org-indent-remove-overlays beg end)
|
||
|
(org-indent-add-overlays beg end)))))
|
||
|
|
||
|
(provide 'org-indent)
|
||
|
|
||
|
;;; org-indent.el ends here
|