summaryrefslogtreecommitdiff
path: root/lisp/org/org-list.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/org/org-list.el')
-rw-r--r--lisp/org/org-list.el310
1 files changed, 242 insertions, 68 deletions
diff --git a/lisp/org/org-list.el b/lisp/org/org-list.el
index 4dd69564403..6c775f7d5d0 100644
--- a/lisp/org/org-list.el
+++ b/lisp/org/org-list.el
@@ -7,7 +7,7 @@
;; Bastien Guerry <bzg AT altern DOT org>
;; Keywords: outlines, hypermedia, calendar, wp
;; Homepage: http://orgmode.org
-;; Version: 6.21b
+;; Version: 6.29c
;;
;; This file is part of GNU Emacs.
;;
@@ -36,6 +36,8 @@
(defvar org-blank-before-new-entry)
(defvar org-M-RET-may-split-line)
+(defvar org-complex-heading-regexp)
+(defvar org-odd-levels-only)
(declare-function org-invisible-p "org" ())
(declare-function org-on-heading-p "org" (&optional invisible-ok))
@@ -48,21 +50,57 @@
(declare-function org-get-indentation "org" (&optional line))
(declare-function org-timer-item "org-timer" (&optional arg))
(declare-function org-combine-plists "org" (&rest plists))
+(declare-function org-entry-get "org" (pom property &optional inherit))
+(declare-function org-narrow-to-subtree "org" ())
+(declare-function org-show-subtree "org" ())
(defgroup org-plain-lists nil
"Options concerning plain lists in Org-mode."
:tag "Org Plain lists"
:group 'org-structure)
-(defcustom org-cycle-include-plain-lists nil
- "Non-nil means, include plain lists into visibility cycling.
-This means that during cycling, plain list items will *temporarily* be
-interpreted as outline headlines with a level given by 1000+i where i is the
-indentation of the bullet. In all other operations, plain list items are
-not seen as headlines. For example, you cannot assign a TODO keyword to
-such an item."
+(defcustom org-cycle-include-plain-lists t
+ "When t, make TAB cycle visibility on plain list items.
+
+Cycling plain lists works only when the cursor is on a plain list
+item. When the cursor is on an outline heading, plain lists are
+treated as text. This is the most stable way of handling this,
+which is why it is the default.
+
+When this is the symbol `integrate', then during cycling, plain
+list items will *temporarily* be interpreted as outline headlines
+with a level given by 1000+i where i is the indentation of the
+bullet. This setting can lead to strange effects when switching
+visibility to `children', because the first \"child\" in a
+subtree decides what children should be listed. If that first
+\"child\" is a plain list item with an implied large level
+number, all true children and grand children of the outline
+heading will be exposed in a children' view."
:group 'org-plain-lists
- :type 'boolean)
+ :type '(choice
+ (const :tag "Never" nil)
+ (const :tag "With cursor in plain list (recommended)" t)
+ (const :tag "As children of outline headings" integrate)))
+
+(defcustom org-list-demote-modify-bullet nil
+ "Default bullet type installed when demoting an item.
+This is an association list, for each bullet type, this alist will point
+to the bulled that should be used when this item is demoted."
+ :group 'org-plain-lists
+ :type '(repeat
+ (cons
+ (choice :tag "If the current bullet is "
+ (const "-")
+ (const "+")
+ (const "*")
+ (const "1.")
+ (const "1)"))
+ (choice :tag "demotion will change it to"
+ (const "-")
+ (const "+")
+ (const "*")
+ (const "1.")
+ (const "1)")))))
(defcustom org-plain-list-ordered-item-terminator t
"The character that makes a line with leading number an ordered list item.
@@ -103,9 +141,15 @@ use \\[org-ctrl-c-ctrl-c] to trigger renumbering."
(defcustom org-provide-checkbox-statistics t
"Non-nil means, update checkbox statistics after insert and toggle.
-When this is set, checkbox statistics is updated each time you either insert
-a new checkbox with \\[org-insert-todo-heading] or toggle a checkbox
-with \\[org-ctrl-c-ctrl-c\\]."
+When this is set, checkbox statistics is updated each time you
+either insert a new checkbox with \\[org-insert-todo-heading] or
+toggle a checkbox with \\[org-ctrl-c-ctrl-c]."
+ :group 'org-plain-lists
+ :type 'boolean)
+
+(defcustom org-hierarchical-checkbox-statistics t
+ "Non-nil means, checkbox statistics counts only the state of direct children.
+When nil, all boxes below the cookie are counted."
:group 'org-plain-lists
:type 'boolean)
@@ -117,7 +161,7 @@ When the indentation would be larger than this, it will become
:type 'integer)
(defvar org-list-beginning-re
- "^\\([ \t]*\\)\\([-+*]\\|[0-9]+[.)]\\) +\\(.*\\)$")
+ "^\\([ \t]*\\)\\([-+]\\|[0-9]+[.)]\\) +\\(.*\\)$")
(defcustom org-list-radio-list-templates
'((latex-mode "% BEGIN RECEIVE ORGLST %n
@@ -159,7 +203,7 @@ list, obtained by prompting the user."
(cond
((eq llt t) "\\([ \t]*\\([-+]\\|\\([0-9]+[.)]\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)")
((= llt ?.) "\\([ \t]*\\([-+]\\|\\([0-9]+\\.\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)")
- ((= llt ?\)) "\\([ \t]*\\([-+]\\|\\([0-9]+))\\)\\|[ \t]+\\*\\)\\( \\|$\\)")
+ ((= llt ?\)) "\\([ \t]*\\([-+]\\|\\([0-9]+)\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)")
(t (error "Invalid value of `org-plain-list-ordered-item-terminator'")))))))
(defun org-at-item-bullet-p ()
@@ -208,7 +252,9 @@ Return t when things worked, nil when we are not in an item."
descp))))
(eow (save-excursion (beginning-of-line 1) (looking-at "[ \t]*")
(match-end 0)))
- (blank-a (cdr (assq 'plain-list-item org-blank-before-new-entry)))
+ (blank-a (if org-empty-line-terminates-plain-lists
+ nil
+ (cdr (assq 'plain-list-item org-blank-before-new-entry))))
(blank (if (eq blank-a 'auto) empty-line-p blank-a))
pos)
(if descp (setq checkbox nil))
@@ -251,6 +297,7 @@ Return t when things worked, nil when we are not in an item."
(defun org-toggle-checkbox (&optional toggle-presence)
"Toggle the checkbox in the current line.
With prefix arg TOGGLE-PRESENCE, add or remove checkboxes.
+With double prefix, set checkbox to [-].
When there is an active region, toggle status or presence of the checkbox
in the first line, and make every item in the region have the same
status or presence, respectively.
@@ -258,24 +305,27 @@ If the cursor is in a headline, apply this to all checkbox items in the
text below the heading."
(interactive "P")
(catch 'exit
- (let (beg end status first-present first-status)
+ (let (beg end status first-present first-status blocked)
(cond
((org-region-active-p)
(setq beg (region-beginning) end (region-end)))
((org-on-heading-p)
(setq beg (point) end (save-excursion (outline-next-heading) (point))))
((org-at-item-checkbox-p)
- (let ((pos (point)))
- (if toggle-presence
+ (save-excursion
+ (if (equal toggle-presence '(4))
(progn
(replace-match "")
(goto-char (match-beginning 0))
(just-one-space))
+ (when (setq blocked (org-checkbox-blocked-p))
+ (error "Checkbox blocked because of unchecked box in line %d"
+ blocked))
(replace-match
- (cond ((member (match-string 0) '("[ ]" "[-]")) "[X]")
+ (cond ((equal toggle-presence '(16)) "[-]")
+ ((member (match-string 0) '("[ ]" "[-]")) "[X]")
(t "[ ]"))
- t t))
- (goto-char pos))
+ t t)))
(throw 'exit t))
((org-at-item-p)
;; add a checkbox
@@ -312,10 +362,53 @@ text below the heading."
(beginning-of-line 2)))))
(org-update-checkbox-count-maybe))
+(defun org-reset-checkbox-state-subtree ()
+ "Reset all checkboxes in an entry subtree."
+ (interactive "*")
+ (save-restriction
+ (save-excursion
+ (org-narrow-to-subtree)
+ (org-show-subtree)
+ (goto-char (point-min))
+ (let ((end (point-max)))
+ (while (< (point) end)
+ (when (org-at-item-checkbox-p)
+ (replace-match "[ ]" t t))
+ (beginning-of-line 2))))
+ (org-update-checkbox-count-maybe)))
+
+(defun org-checkbox-blocked-p ()
+ "Is the current checkbox blocked from for being checked now?
+A checkbox is blocked if all of the following conditions are fulfilled:
+
+1. The checkbox is not checked already.
+2. The current entry has the ORDERED property set.
+3. There is an unchecked checkbox in this entry before the current line."
+ (catch 'exit
+ (save-match-data
+ (save-excursion
+ (unless (org-at-item-checkbox-p) (throw 'exit nil))
+ (when (equal (match-string 0) "[X]")
+ ;; the box is already checked!
+ (throw 'exit nil))
+ (let ((end (point-at-bol)))
+ (condition-case nil (org-back-to-heading t)
+ (error (throw 'exit nil)))
+ (unless (org-entry-get nil "ORDERED") (throw 'exit nil))
+ (if (re-search-forward "^[ \t]*[-+*0-9.)] \\[[- ]\\]" end t)
+ (org-current-line)
+ nil))))))
+
+(defvar org-checkbox-statistics-hook nil
+ "Hook that is run whenever Org thinks checkbox statistics should be updated.
+This hook runs even if `org-provide-checkbox-statistics' is nil, to it can
+be used to implement alternative ways of collecting statistics information.")
+
(defun org-update-checkbox-count-maybe ()
"Update checkbox statistics unless turned off by user."
(when org-provide-checkbox-statistics
- (org-update-checkbox-count)))
+ (org-update-checkbox-count))
+ (run-hooks 'org-checkbox-statistics-hook))
(defun org-update-checkbox-count (&optional all)
"Update the checkbox statistics in the current section.
@@ -335,6 +428,10 @@ the whole buffer."
(re-find (concat re "\\|" re-box))
beg-cookie end-cookie is-percent c-on c-off lim
eline curr-ind next-ind continue-from startsearch
+ (recursive
+ (or (not org-hierarchical-checkbox-statistics)
+ (string-match "\\<recursive\\>"
+ (or (org-entry-get nil "COOKIE_DATA") ""))))
(cstat 0)
)
(when all
@@ -342,8 +439,15 @@ the whole buffer."
(outline-next-heading)
(setq beg (point) end (point-max)))
(goto-char end)
- ;; find each statistic cookie
- (while (re-search-backward re-find beg t)
+ ;; find each statistics cookie
+ (while (and (re-search-backward re-find beg t)
+ (not (save-match-data
+ (and (org-on-heading-p)
+ (string-match "\\<todo\\>"
+ (downcase
+ (or (org-entry-get
+ nil "COOKIE_DATA")
+ "")))))))
(setq beg-cookie (match-beginning 1)
end-cookie (match-end 1)
cstat (+ cstat (if end-cookie 1 0))
@@ -365,17 +469,21 @@ the whole buffer."
(org-beginning-of-item)
(setq curr-ind (org-get-indentation))
(setq next-ind curr-ind)
- (while (and (bolp) (org-at-item-p) (= curr-ind next-ind))
+ (while (and (bolp) (org-at-item-p)
+ (if recursive
+ (<= curr-ind next-ind)
+ (= curr-ind next-ind)))
(save-excursion (end-of-line) (setq eline (point)))
(if (re-search-forward re-box eline t)
(if (member (match-string 2) '("[ ]" "[-]"))
(setq c-off (1+ c-off))
- (setq c-on (1+ c-on))
- )
- )
- (org-end-of-item)
- (setq next-ind (org-get-indentation))
- )))
+ (setq c-on (1+ c-on))))
+ (if (not recursive)
+ (org-end-of-item)
+ (end-of-line)
+ (when (re-search-forward org-list-beginning-re lim t)
+ (beginning-of-line)))
+ (setq next-ind (org-get-indentation)))))
(goto-char continue-from)
;; update cookie
(when end-cookie
@@ -408,11 +516,13 @@ the whole buffer."
The face will be `org-done' when all relevant boxes are checked. Otherwise
it will be `org-todo'."
(if (match-end 1)
- (if (equal (match-string 1) "100%") 'org-done 'org-todo)
+ (if (equal (match-string 1) "100%")
+ 'org-checkbox-statistics-done
+ 'org-checkbox-statistics-todo)
(if (and (> (match-end 2) (match-beginning 2))
(equal (match-string 2) (match-string 3)))
- 'org-done
- 'org-todo)))
+ 'org-checkbox-statistics-done
+ 'org-checkbox-statistics-todo)))
(defun org-beginning-of-item ()
"Go to the beginning of the current hand-formatted item.
@@ -513,11 +623,12 @@ Error if not at a plain list, or if this is the first item in the list."
(error "On first item")))))
(defun org-first-list-item-p ()
- "Is this heading the item in a plain list?"
+ "Is this heading the first item in a plain list?"
(unless (org-at-item-p)
(error "Not at a plain list item"))
- (org-beginning-of-item)
- (= (point) (save-excursion (org-beginning-of-item-list))))
+ (save-excursion
+ (org-beginning-of-item)
+ (= (point) (save-excursion (org-beginning-of-item-list)))))
(defun org-move-item-down ()
"Move the plain list item at point down, i.e. swap with following item.
@@ -705,7 +816,7 @@ with something like \"1.\" or \"2)\"."
(org-beginning-of-item-list)
(setq bobp (bobp))
(looking-at "[ \t]*[0-9]+\\([.)]\\)")
- (setq fmt (concat "%d" (match-string 1)))
+ (setq fmt (concat "%d" (or (match-string 1) ".")))
(beginning-of-line 0)
;; walk forward and replace these numbers
(catch 'exit
@@ -726,7 +837,7 @@ with something like \"1.\" or \"2)\"."
(goto-line line)
(org-move-to-column col)))
-(defun org-fix-bullet-type ()
+(defun org-fix-bullet-type (&optional force-bullet)
"Make sure all items in this list have the same bullet as the first item.
Also, fix the indentation."
(interactive)
@@ -740,7 +851,7 @@ Also, fix the indentation."
(beginning-of-line 1)
;; find out what the bullet type is
(looking-at "[ \t]*\\(\\S-+\\)")
- (setq bullet (concat (match-string 1) " "))
+ (setq bullet (concat (or force-bullet (match-string 1)) " "))
(if (and org-list-two-spaces-after-bullet-regexp
(string-match org-list-two-spaces-after-bullet-regexp bullet))
(setq bullet (concat bullet " ")))
@@ -759,7 +870,7 @@ Also, fix the indentation."
(skip-chars-forward " \t")
(looking-at "\\S-+ *")
(setq oldbullet (match-string 0))
- (replace-match bullet)
+ (unless (equal bullet oldbullet) (replace-match bullet))
(org-shift-item-indentation (- (length bullet) (length oldbullet))))))
(goto-line line)
(org-move-to-column col)
@@ -807,7 +918,6 @@ I.e. to the first item in this list."
(when (org-at-item-p) (setq pos (point-at-bol)))))))
(goto-char pos)))
-
(defun org-end-of-item-list ()
"Go to the end of the current item list.
I.e. to the text after the last item."
@@ -822,7 +932,9 @@ I.e. to the text after the last item."
(catch 'next
(beginning-of-line 2)
(if (looking-at "[ \t]*$")
- (throw (if (eobp) 'exit 'next) t))
+ (if (eobp)
+ (progn (setq pos (point)) (throw 'exit t))
+ (throw 'next t)))
(skip-chars-forward " \t") (setq ind1 (current-column))
(if (or (< ind1 ind)
(and (= ind1 ind)
@@ -845,22 +957,25 @@ I.e. to the text after the last item."
(defun org-indent-item (arg)
"Indent a local list item."
(interactive "p")
+ (and (org-region-active-p) (org-cursor-to-region-beginning))
(unless (org-at-item-p)
(error "Not on an item"))
- (save-excursion
- (let (beg end ind ind1 tmp delta ind-down ind-up)
+ (let (beg end ind ind1 ind-bul delta ind-down ind-up firstp)
+ (setq firstp (org-first-list-item-p))
+ (save-excursion
+ (setq end (and (org-region-active-p) (region-end)))
(if (memq last-command '(org-shiftmetaright org-shiftmetaleft))
(setq beg org-last-indent-begin-marker
end org-last-indent-end-marker)
(org-beginning-of-item)
(setq beg (move-marker org-last-indent-begin-marker (point)))
(org-end-of-item)
- (setq end (move-marker org-last-indent-end-marker (point))))
+ (setq end (move-marker org-last-indent-end-marker (or end (point)))))
(goto-char beg)
- (setq tmp (org-item-indent-positions)
- ind (car tmp)
- ind-down (nth 2 tmp)
- ind-up (nth 1 tmp)
+ (setq ind-bul (org-item-indent-positions)
+ ind (caar ind-bul)
+ ind-down (car (nth 2 ind-bul))
+ ind-up (car (nth 1 ind-bul))
delta (if (> arg 0)
(if ind-down (- ind-down ind) 2)
(if ind-up (- ind-up ind) -2)))
@@ -870,13 +985,16 @@ I.e. to the text after the last item."
(skip-chars-forward " \t") (setq ind1 (current-column))
(delete-region (point-at-bol) (point))
(or (eolp) (org-indent-to-column (+ ind1 delta)))
- (beginning-of-line 2))))
- (org-fix-bullet-type)
- (org-maybe-renumber-ordered-list-safe)
- (save-excursion
- (beginning-of-line 0)
- (condition-case nil (org-beginning-of-item) (error nil))
- (org-maybe-renumber-ordered-list-safe)))
+ (beginning-of-line 2)))
+ (org-fix-bullet-type
+ (and (> arg 0)
+ (not firstp)
+ (cdr (assoc (cdr (nth 0 ind-bul)) org-list-demote-modify-bullet))))
+ (org-maybe-renumber-ordered-list-safe)
+ (save-excursion
+ (beginning-of-line 0)
+ (condition-case nil (org-beginning-of-item) (error nil))
+ (org-maybe-renumber-ordered-list-safe))))
(defun org-item-indent-positions ()
"Return indentation for plain list items.
@@ -885,13 +1003,15 @@ parent indentation and the indentation a child should have.
Assumes cursor in item line."
(let* ((bolpos (point-at-bol))
(ind (org-get-indentation))
- ind-down ind-up pos)
+ (bullet (org-get-bullet))
+ ind-down ind-up bullet-up bullet-down pos)
(save-excursion
(org-beginning-of-item-list)
(skip-chars-backward "\n\r \t")
(when (org-in-item-p)
(org-beginning-of-item)
- (setq ind-up (org-get-indentation))))
+ (setq ind-up (org-get-indentation))
+ (setq bullet-up (org-get-bullet))))
(setq pos (point))
(save-excursion
(cond
@@ -899,14 +1019,30 @@ Assumes cursor in item line."
(error nil))
(or (forward-char 1) t)
(re-search-forward "^\\([ \t]*\\([-+]\\|\\([0-9]+[.)]\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)" bolpos t))
- (setq ind-down (org-get-indentation)))
+ (setq ind-down (org-get-indentation)
+ bullet-down (org-get-bullet)))
((and (goto-char pos)
(org-at-item-p))
(goto-char (match-end 0))
(skip-chars-forward " \t")
- (setq ind-down (current-column)))))
- (list ind ind-up ind-down)))
-
+ (setq ind-down (current-column)
+ bullet-down (org-get-bullet)))))
+ (if (and bullet-down (string-match "\\`[0-9]+\\(\\.\\|)\\)\\'" bullet-down))
+ (setq bullet-down (concat "1" (match-string 1 bullet-down))))
+ (if (and bullet-up (string-match "\\`[0-9]+\\(\\.\\|)\\)\\'" bullet-up))
+ (setq bullet-up (concat "1" (match-string 1 bullet-up))))
+ (if (and bullet (string-match "\\`[0-9]+\\(\\.\\|)\\)\\'" bullet))
+ (setq bullet (concat "1" (match-string 1 bullet))))
+ (list (cons ind bullet)
+ (cons ind-up bullet-up)
+ (cons ind-down bullet-down))))
+
+(defun org-get-bullet ()
+ (save-excursion
+ (goto-char (point-at-bol))
+ (and (looking-at
+ "^\\([ \t]*\\([-+]\\|\\([0-9]+[.)]\\)\\)\\|[ \t]+\\(\\*\\)\\)\\( \\|$\\)")
+ (or (match-string 2) (match-string 4)))))
;;; Send and receive lists
@@ -968,16 +1104,54 @@ cdr is the indentation string."
(progn (goto-char (point-min)) (point))
(cons (match-beginning 0) (match-string 1)))))
+(defun org-list-goto-true-beginning ()
+ "Go to the beginning of the list at point."
+ (beginning-of-line 1)
+ (while (looking-at org-list-beginning-re)
+ (beginning-of-line 0))
+ (progn
+ (re-search-forward org-list-beginning-re nil t)
+ (goto-char (match-beginning 0))))
+
+(defun org-list-make-subtree ()
+ "Convert the plain list at point into a subtree."
+ (interactive)
+ (org-list-goto-true-beginning)
+ (let ((list (org-list-parse-list t)) nstars)
+ (save-excursion
+ (if (condition-case nil
+ (org-back-to-heading)
+ (error nil))
+ (progn (re-search-forward org-complex-heading-regexp nil t)
+ (setq nstars (length (match-string 1))))
+ (setq nstars 0)))
+ (org-list-make-subtrees list (1+ nstars))))
+
+(defun org-list-make-subtrees (list level)
+ "Convert LIST into subtrees starting at LEVEL."
+ (if (symbolp (car list))
+ (org-list-make-subtrees (cdr list) level)
+ (mapcar (lambda (item)
+ (if (stringp item)
+ (insert (make-string
+ (if org-odd-levels-only
+ (1- (* 2 level)) level) ?*) " " item "\n")
+ (org-list-make-subtrees item (1+ level))))
+ list)))
+
(defun org-list-end (indent)
"Return the position of the end of the list.
-INDENT is the indentation of the list."
+INDENT is the indentation of the list, as a string."
(save-excursion
(catch 'exit
(while (or (looking-at org-list-beginning-re)
- (looking-at (concat "^" indent "[ \t]+\\|^$")))
+ (looking-at (concat "^" indent "[ \t]+\\|^$"))
+ (> (or (get-text-property (point) 'original-indentation) -1)
+ (length indent)))
(if (eq (point) (point-max))
(throw 'exit (point-max)))
- (forward-line 1))) (point)))
+ (forward-line 1)))
+ (point)))
(defun org-list-insert-radio-list ()
"Insert a radio list template appropriate for this major mode."
@@ -1002,7 +1176,7 @@ this list."
(catch 'exit
(unless (org-at-item-p) (error "Not at a list"))
(save-excursion
- (goto-char (car (org-list-item-beginning)))
+ (org-list-goto-true-beginning)
(beginning-of-line 0)
(unless (looking-at "#\\+ORGLST: *SEND +\\([a-zA-Z0-9_]+\\) +\\([^ \t\r\n]+\\)\\( +.*\\)?")
(if maybe
@@ -1150,7 +1324,7 @@ with overruling parameters for `org-list-to-generic'."
LIST is as returnd by `org-list-parse-list'. PARAMS is a property list
with overruling parameters for `org-list-to-generic'."
(org-list-to-generic
- list
+ list
(org-combine-plists
'(:splicep nil :ostart "@itemize @minus" :oend "@end itemize"
:ustart "@enumerate" :uend "@end enumerate"