Commit | Line | Data |
---|---|---|
47ffc456 CD |
1 | ;;; org-list.el --- Plain lists for Org-mode |
2 | ;; | |
1e4f816a CD |
3 | ;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 |
4 | ;; Free Software Foundation, Inc. | |
47ffc456 CD |
5 | ;; |
6 | ;; Author: Carsten Dominik <carsten at orgmode dot org> | |
33306645 | 7 | ;; Bastien Guerry <bzg AT altern DOT org> |
47ffc456 CD |
8 | ;; Keywords: outlines, hypermedia, calendar, wp |
9 | ;; Homepage: http://orgmode.org | |
c8d0cf5c | 10 | ;; Version: 6.29c |
47ffc456 CD |
11 | ;; |
12 | ;; This file is part of GNU Emacs. | |
13 | ;; | |
14 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
15 | ;; it under the terms of the GNU General Public License as published by | |
16 | ;; the Free Software Foundation, either version 3 of the License, or | |
17 | ;; (at your option) any later version. | |
18 | ||
19 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
33306645 | 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
47ffc456 CD |
22 | ;; GNU General Public License for more details. |
23 | ||
24 | ;; You should have received a copy of the GNU General Public License | |
25 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
26 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
27 | ;; | |
28 | ;;; Commentary: | |
29 | ||
30 | ;; This file contains the code dealing with plain lists in Org-mode. | |
31 | ||
32 | ;;; Code: | |
33 | ||
34 | (require 'org-macs) | |
35 | (require 'org-compat) | |
36 | ||
37 | (defvar org-blank-before-new-entry) | |
38 | (defvar org-M-RET-may-split-line) | |
c8d0cf5c CD |
39 | (defvar org-complex-heading-regexp) |
40 | (defvar org-odd-levels-only) | |
47ffc456 CD |
41 | |
42 | (declare-function org-invisible-p "org" ()) | |
43 | (declare-function org-on-heading-p "org" (&optional invisible-ok)) | |
9fc10007 GM |
44 | (declare-function outline-next-heading "outline" ()) |
45 | (declare-function outline-back-to-heading "outline" (&optional invisible-ok)) | |
47ffc456 CD |
46 | (declare-function org-back-to-heading "org" (&optional invisible-ok)) |
47 | (declare-function org-back-over-empty-lines "org" ()) | |
48 | (declare-function org-skip-whitespace "org" ()) | |
49 | (declare-function org-trim "org" (s)) | |
50 | (declare-function org-get-indentation "org" (&optional line)) | |
ff4be292 | 51 | (declare-function org-timer-item "org-timer" (&optional arg)) |
0bd48b37 | 52 | (declare-function org-combine-plists "org" (&rest plists)) |
c8d0cf5c CD |
53 | (declare-function org-entry-get "org" (pom property &optional inherit)) |
54 | (declare-function org-narrow-to-subtree "org" ()) | |
55 | (declare-function org-show-subtree "org" ()) | |
47ffc456 CD |
56 | |
57 | (defgroup org-plain-lists nil | |
58 | "Options concerning plain lists in Org-mode." | |
59 | :tag "Org Plain lists" | |
60 | :group 'org-structure) | |
61 | ||
c8d0cf5c CD |
62 | (defcustom org-cycle-include-plain-lists t |
63 | "When t, make TAB cycle visibility on plain list items. | |
64 | ||
65 | Cycling plain lists works only when the cursor is on a plain list | |
66 | item. When the cursor is on an outline heading, plain lists are | |
67 | treated as text. This is the most stable way of handling this, | |
68 | which is why it is the default. | |
69 | ||
70 | When this is the symbol `integrate', then during cycling, plain | |
71 | list items will *temporarily* be interpreted as outline headlines | |
72 | with a level given by 1000+i where i is the indentation of the | |
73 | bullet. This setting can lead to strange effects when switching | |
74 | visibility to `children', because the first \"child\" in a | |
75 | subtree decides what children should be listed. If that first | |
76 | \"child\" is a plain list item with an implied large level | |
77 | number, all true children and grand children of the outline | |
78 | heading will be exposed in a children' view." | |
47ffc456 | 79 | :group 'org-plain-lists |
c8d0cf5c CD |
80 | :type '(choice |
81 | (const :tag "Never" nil) | |
82 | (const :tag "With cursor in plain list (recommended)" t) | |
83 | (const :tag "As children of outline headings" integrate))) | |
84 | ||
85 | (defcustom org-list-demote-modify-bullet nil | |
86 | "Default bullet type installed when demoting an item. | |
87 | This is an association list, for each bullet type, this alist will point | |
88 | to the bulled that should be used when this item is demoted." | |
89 | :group 'org-plain-lists | |
90 | :type '(repeat | |
91 | (cons | |
92 | (choice :tag "If the current bullet is " | |
93 | (const "-") | |
94 | (const "+") | |
95 | (const "*") | |
96 | (const "1.") | |
97 | (const "1)")) | |
98 | (choice :tag "demotion will change it to" | |
99 | (const "-") | |
100 | (const "+") | |
101 | (const "*") | |
102 | (const "1.") | |
103 | (const "1)"))))) | |
47ffc456 CD |
104 | |
105 | (defcustom org-plain-list-ordered-item-terminator t | |
106 | "The character that makes a line with leading number an ordered list item. | |
107 | Valid values are ?. and ?\). To get both terminators, use t. While | |
108 | ?. may look nicer, it creates the danger that a line with leading | |
109 | number may be incorrectly interpreted as an item. ?\) therefore is | |
110 | the safe choice." | |
111 | :group 'org-plain-lists | |
112 | :type '(choice (const :tag "dot like in \"2.\"" ?.) | |
113 | (const :tag "paren like in \"2)\"" ?\)) | |
114 | (const :tab "both" t))) | |
115 | ||
ce4fdcb9 CD |
116 | (defcustom org-list-two-spaces-after-bullet-regexp nil |
117 | "A regular expression matching bullets that should have 2 spaces after them. | |
118 | When nil, no bullet will have two spaces after them. | |
33306645 | 119 | When a string, it will be used as a regular expression. When the bullet |
ce4fdcb9 | 120 | type of a list is changed, the new bullet type will be matched against this |
33306645 | 121 | regexp. If it matches, there will be two spaces instead of one after |
ce4fdcb9 CD |
122 | the bullet in each item of he list." |
123 | :group 'org-plain-list | |
124 | :type '(choice | |
125 | (const :tag "never" nil) | |
126 | (regexp))) | |
127 | ||
47ffc456 CD |
128 | (defcustom org-empty-line-terminates-plain-lists nil |
129 | "Non-nil means, an empty line ends all plain list levels. | |
805b5d9c | 130 | When nil, empty lines are part of the preceding item." |
47ffc456 CD |
131 | :group 'org-plain-lists |
132 | :type 'boolean) | |
133 | ||
134 | (defcustom org-auto-renumber-ordered-lists t | |
135 | "Non-nil means, automatically renumber ordered plain lists. | |
136 | Renumbering happens when the sequence have been changed with | |
137 | \\[org-shiftmetaup] or \\[org-shiftmetadown]. After other editing commands, | |
138 | use \\[org-ctrl-c-ctrl-c] to trigger renumbering." | |
139 | :group 'org-plain-lists | |
140 | :type 'boolean) | |
141 | ||
142 | (defcustom org-provide-checkbox-statistics t | |
143 | "Non-nil means, update checkbox statistics after insert and toggle. | |
c8d0cf5c CD |
144 | When this is set, checkbox statistics is updated each time you |
145 | either insert a new checkbox with \\[org-insert-todo-heading] or | |
146 | toggle a checkbox with \\[org-ctrl-c-ctrl-c]." | |
147 | :group 'org-plain-lists | |
148 | :type 'boolean) | |
149 | ||
150 | (defcustom org-hierarchical-checkbox-statistics t | |
151 | "Non-nil means, checkbox statistics counts only the state of direct children. | |
152 | When nil, all boxes below the cookie are counted." | |
47ffc456 CD |
153 | :group 'org-plain-lists |
154 | :type 'boolean) | |
155 | ||
156 | (defcustom org-description-max-indent 20 | |
157 | "Maximum indentation for the second line of a description list. | |
158 | When the indentation would be larger than this, it will become | |
159 | 5 characters instead." | |
160 | :group 'org-plain-lists | |
161 | :type 'integer) | |
162 | ||
163 | (defvar org-list-beginning-re | |
c8d0cf5c | 164 | "^\\([ \t]*\\)\\([-+]\\|[0-9]+[.)]\\) +\\(.*\\)$") |
47ffc456 CD |
165 | |
166 | (defcustom org-list-radio-list-templates | |
167 | '((latex-mode "% BEGIN RECEIVE ORGLST %n | |
168 | % END RECEIVE ORGLST %n | |
169 | \\begin{comment} | |
170 | #+ORGLST: SEND %n org-list-to-latex | |
171 | | | | | |
172 | \\end{comment}\n") | |
173 | (texinfo-mode "@c BEGIN RECEIVE ORGLST %n | |
174 | @c END RECEIVE ORGLST %n | |
175 | @ignore | |
176 | #+ORGLST: SEND %n org-list-to-texinfo | |
177 | | | | | |
178 | @end ignore\n") | |
179 | (html-mode "<!-- BEGIN RECEIVE ORGLST %n --> | |
180 | <!-- END RECEIVE ORGLST %n --> | |
181 | <!-- | |
182 | #+ORGLST: SEND %n org-list-to-html | |
183 | | | | | |
184 | -->\n")) | |
185 | "Templates for radio lists in different major modes. | |
186 | All occurrences of %n in a template will be replaced with the name of the | |
187 | list, obtained by prompting the user." | |
188 | :group 'org-plain-lists | |
189 | :type '(repeat | |
190 | (list (symbol :tag "Major mode") | |
191 | (string :tag "Format")))) | |
192 | ||
193 | ;;;; Plain list items, including checkboxes | |
194 | ||
195 | ;;; Plain list items | |
196 | ||
197 | (defun org-at-item-p () | |
198 | "Is point in a line starting a hand-formatted item?" | |
199 | (let ((llt org-plain-list-ordered-item-terminator)) | |
200 | (save-excursion | |
201 | (goto-char (point-at-bol)) | |
202 | (looking-at | |
203 | (cond | |
204 | ((eq llt t) "\\([ \t]*\\([-+]\\|\\([0-9]+[.)]\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)") | |
205 | ((= llt ?.) "\\([ \t]*\\([-+]\\|\\([0-9]+\\.\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)") | |
c8d0cf5c | 206 | ((= llt ?\)) "\\([ \t]*\\([-+]\\|\\([0-9]+)\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)") |
47ffc456 CD |
207 | (t (error "Invalid value of `org-plain-list-ordered-item-terminator'"))))))) |
208 | ||
65c439fd CD |
209 | (defun org-at-item-bullet-p () |
210 | "Is point at the bullet of a plain list item?" | |
211 | (and (org-at-item-p) | |
212 | (not (member (char-after) '(?\ ?\t))) | |
213 | (< (point) (match-end 0)))) | |
214 | ||
47ffc456 CD |
215 | (defun org-in-item-p () |
216 | "It the cursor inside a plain list item. | |
217 | Does not have to be the first line." | |
218 | (save-excursion | |
219 | (condition-case nil | |
220 | (progn | |
221 | (org-beginning-of-item) | |
222 | (org-at-item-p) | |
223 | t) | |
224 | (error nil)))) | |
225 | ||
226 | (defun org-insert-item (&optional checkbox) | |
227 | "Insert a new item at the current level. | |
228 | Return t when things worked, nil when we are not in an item." | |
229 | (when (save-excursion | |
230 | (condition-case nil | |
231 | (progn | |
232 | (org-beginning-of-item) | |
233 | (org-at-item-p) | |
234 | (if (org-invisible-p) (error "Invisible item")) | |
235 | t) | |
236 | (error nil))) | |
237 | (let* ((bul (match-string 0)) | |
238 | (descp (save-excursion (goto-char (match-beginning 0)) | |
239 | (beginning-of-line 1) | |
240 | (save-match-data | |
ff4be292 CD |
241 | (and (looking-at "[ \t]*\\(.*?\\) ::") |
242 | (match-string 1))))) | |
0bd48b37 CD |
243 | (empty-line-p (save-excursion |
244 | (goto-char (match-beginning 0)) | |
245 | (and (not (bobp)) | |
246 | (or (beginning-of-line 0) t) | |
247 | (save-match-data | |
248 | (looking-at "[ \t]*$"))))) | |
ff4be292 CD |
249 | (timerp (and descp |
250 | (save-match-data | |
251 | (string-match "^[-+*][ \t]+[0-9]+:[0-9]+:[0-9]+$" | |
252 | descp)))) | |
47ffc456 CD |
253 | (eow (save-excursion (beginning-of-line 1) (looking-at "[ \t]*") |
254 | (match-end 0))) | |
c8d0cf5c CD |
255 | (blank-a (if org-empty-line-terminates-plain-lists |
256 | nil | |
257 | (cdr (assq 'plain-list-item org-blank-before-new-entry)))) | |
0bd48b37 | 258 | (blank (if (eq blank-a 'auto) empty-line-p blank-a)) |
47ffc456 CD |
259 | pos) |
260 | (if descp (setq checkbox nil)) | |
ff4be292 CD |
261 | (if timerp |
262 | (progn (org-timer-item) t) | |
263 | (cond | |
264 | ((and (org-at-item-p) (<= (point) eow)) | |
265 | ;; before the bullet | |
266 | (beginning-of-line 1) | |
267 | (open-line (if blank 2 1))) | |
268 | ((<= (point) eow) | |
269 | (beginning-of-line 1)) | |
270 | (t | |
271 | (unless (org-get-alist-option org-M-RET-may-split-line 'item) | |
272 | (end-of-line 1) | |
273 | (delete-horizontal-space)) | |
274 | (newline (if blank 2 1)))) | |
275 | (insert bul | |
276 | (if checkbox "[ ]" "") | |
277 | (if descp (concat (if checkbox " " "") | |
278 | (read-string "Term: ") " :: ") "")) | |
279 | (just-one-space) | |
280 | (setq pos (point)) | |
281 | (end-of-line 1) | |
282 | (unless (= (point) pos) (just-one-space) (backward-delete-char 1))) | |
283 | (org-maybe-renumber-ordered-list) | |
284 | (and checkbox (org-update-checkbox-count-maybe)) | |
285 | t))) | |
47ffc456 CD |
286 | |
287 | ;;; Checkboxes | |
288 | ||
289 | (defun org-at-item-checkbox-p () | |
290 | "Is point at a line starting a plain-list item with a checklet?" | |
291 | (and (org-at-item-p) | |
292 | (save-excursion | |
293 | (goto-char (match-end 0)) | |
294 | (skip-chars-forward " \t") | |
295 | (looking-at "\\[[- X]\\]")))) | |
296 | ||
d6685abc CD |
297 | (defun org-toggle-checkbox (&optional toggle-presence) |
298 | "Toggle the checkbox in the current line. | |
299 | With prefix arg TOGGLE-PRESENCE, add or remove checkboxes. | |
c8d0cf5c | 300 | With double prefix, set checkbox to [-]. |
d6685abc CD |
301 | When there is an active region, toggle status or presence of the checkbox |
302 | in the first line, and make every item in the region have the same | |
805b5d9c | 303 | status or presence, respectively. |
a2a2e7fb CD |
304 | If the cursor is in a headline, apply this to all checkbox items in the |
305 | text below the heading." | |
47ffc456 CD |
306 | (interactive "P") |
307 | (catch 'exit | |
c8d0cf5c | 308 | (let (beg end status first-present first-status blocked) |
47ffc456 CD |
309 | (cond |
310 | ((org-region-active-p) | |
311 | (setq beg (region-beginning) end (region-end))) | |
312 | ((org-on-heading-p) | |
313 | (setq beg (point) end (save-excursion (outline-next-heading) (point)))) | |
314 | ((org-at-item-checkbox-p) | |
c8d0cf5c CD |
315 | (save-excursion |
316 | (if (equal toggle-presence '(4)) | |
d6685abc CD |
317 | (progn |
318 | (replace-match "") | |
319 | (goto-char (match-beginning 0)) | |
320 | (just-one-space)) | |
c8d0cf5c CD |
321 | (when (setq blocked (org-checkbox-blocked-p)) |
322 | (error "Checkbox blocked because of unchecked box in line %d" | |
323 | blocked)) | |
d6685abc | 324 | (replace-match |
c8d0cf5c CD |
325 | (cond ((equal toggle-presence '(16)) "[-]") |
326 | ((member (match-string 0) '("[ ]" "[-]")) "[X]") | |
d6685abc | 327 | (t "[ ]")) |
c8d0cf5c | 328 | t t))) |
47ffc456 | 329 | (throw 'exit t)) |
d6685abc CD |
330 | ((org-at-item-p) |
331 | ;; add a checkbox | |
332 | (save-excursion | |
333 | (goto-char (match-end 0)) | |
334 | (insert "[ ] ")) | |
335 | (throw 'exit t)) | |
47ffc456 | 336 | (t (error "Not at a checkbox or heading, and no active region"))) |
d6685abc | 337 | (setq end (move-marker (make-marker) end)) |
47ffc456 CD |
338 | (save-excursion |
339 | (goto-char beg) | |
d6685abc | 340 | (setq first-present (org-at-item-checkbox-p) |
a2a2e7fb CD |
341 | first-status |
342 | (save-excursion | |
343 | (and (re-search-forward "[ \t]\\(\\[[ X]\\]\\)" end t) | |
344 | (equal (match-string 1) "[X]")))) | |
47ffc456 | 345 | (while (< (point) end) |
d6685abc CD |
346 | (if toggle-presence |
347 | (cond | |
348 | ((and first-present (org-at-item-checkbox-p)) | |
349 | (save-excursion | |
350 | (replace-match "") | |
351 | (goto-char (match-beginning 0)) | |
352 | (just-one-space))) | |
353 | ((and (not first-present) (not (org-at-item-checkbox-p)) | |
354 | (org-at-item-p)) | |
355 | (save-excursion | |
356 | (goto-char (match-end 0)) | |
357 | (insert "[ ] ")))) | |
358 | (when (org-at-item-checkbox-p) | |
359 | (setq status (equal (match-string 0) "[X]")) | |
360 | (replace-match | |
361 | (if first-status "[ ]" "[X]") t t))) | |
47ffc456 CD |
362 | (beginning-of-line 2))))) |
363 | (org-update-checkbox-count-maybe)) | |
364 | ||
c8d0cf5c CD |
365 | (defun org-reset-checkbox-state-subtree () |
366 | "Reset all checkboxes in an entry subtree." | |
367 | (interactive "*") | |
368 | (save-restriction | |
369 | (save-excursion | |
370 | (org-narrow-to-subtree) | |
371 | (org-show-subtree) | |
372 | (goto-char (point-min)) | |
373 | (let ((end (point-max))) | |
374 | (while (< (point) end) | |
375 | (when (org-at-item-checkbox-p) | |
376 | (replace-match "[ ]" t t)) | |
377 | (beginning-of-line 2)))) | |
378 | (org-update-checkbox-count-maybe))) | |
379 | ||
380 | (defun org-checkbox-blocked-p () | |
381 | "Is the current checkbox blocked from for being checked now? | |
382 | A checkbox is blocked if all of the following conditions are fulfilled: | |
383 | ||
384 | 1. The checkbox is not checked already. | |
385 | 2. The current entry has the ORDERED property set. | |
386 | 3. There is an unchecked checkbox in this entry before the current line." | |
387 | (catch 'exit | |
388 | (save-match-data | |
389 | (save-excursion | |
390 | (unless (org-at-item-checkbox-p) (throw 'exit nil)) | |
391 | (when (equal (match-string 0) "[X]") | |
392 | ;; the box is already checked! | |
393 | (throw 'exit nil)) | |
394 | (let ((end (point-at-bol))) | |
395 | (condition-case nil (org-back-to-heading t) | |
396 | (error (throw 'exit nil))) | |
397 | (unless (org-entry-get nil "ORDERED") (throw 'exit nil)) | |
398 | (if (re-search-forward "^[ \t]*[-+*0-9.)] \\[[- ]\\]" end t) | |
399 | (org-current-line) | |
400 | nil)))))) | |
401 | ||
402 | (defvar org-checkbox-statistics-hook nil | |
403 | "Hook that is run whenever Org thinks checkbox statistics should be updated. | |
404 | This hook runs even if `org-provide-checkbox-statistics' is nil, to it can | |
405 | be used to implement alternative ways of collecting statistics information.") | |
406 | ||
47ffc456 CD |
407 | (defun org-update-checkbox-count-maybe () |
408 | "Update checkbox statistics unless turned off by user." | |
409 | (when org-provide-checkbox-statistics | |
c8d0cf5c CD |
410 | (org-update-checkbox-count)) |
411 | (run-hooks 'org-checkbox-statistics-hook)) | |
47ffc456 CD |
412 | |
413 | (defun org-update-checkbox-count (&optional all) | |
414 | "Update the checkbox statistics in the current section. | |
415 | This will find all statistic cookies like [57%] and [6/12] and update them | |
416 | with the current numbers. With optional prefix argument ALL, do this for | |
417 | the whole buffer." | |
418 | (interactive "P") | |
419 | (save-excursion | |
420 | (let* ((buffer-invisibility-spec (org-inhibit-invisibility)) ; Emacs 21 | |
421 | (beg (condition-case nil | |
ce4fdcb9 | 422 | (progn (org-back-to-heading) (point)) |
47ffc456 CD |
423 | (error (point-min)))) |
424 | (end (move-marker (make-marker) | |
425 | (progn (outline-next-heading) (point)))) | |
426 | (re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)") | |
427 | (re-box "^[ \t]*\\([-+*]\\|[0-9]+[.)]\\) +\\(\\[[- X]\\]\\)") | |
428 | (re-find (concat re "\\|" re-box)) | |
429 | beg-cookie end-cookie is-percent c-on c-off lim | |
33306645 | 430 | eline curr-ind next-ind continue-from startsearch |
c8d0cf5c CD |
431 | (recursive |
432 | (or (not org-hierarchical-checkbox-statistics) | |
433 | (string-match "\\<recursive\\>" | |
434 | (or (org-entry-get nil "COOKIE_DATA") "")))) | |
33306645 CD |
435 | (cstat 0) |
436 | ) | |
47ffc456 CD |
437 | (when all |
438 | (goto-char (point-min)) | |
439 | (outline-next-heading) | |
440 | (setq beg (point) end (point-max))) | |
441 | (goto-char end) | |
c8d0cf5c CD |
442 | ;; find each statistics cookie |
443 | (while (and (re-search-backward re-find beg t) | |
444 | (not (save-match-data | |
445 | (and (org-on-heading-p) | |
446 | (string-match "\\<todo\\>" | |
447 | (downcase | |
448 | (or (org-entry-get | |
449 | nil "COOKIE_DATA") | |
450 | ""))))))) | |
47ffc456 | 451 | (setq beg-cookie (match-beginning 1) |
33306645 | 452 | end-cookie (match-end 1) |
47ffc456 CD |
453 | cstat (+ cstat (if end-cookie 1 0)) |
454 | startsearch (point-at-eol) | |
a2a2e7fb | 455 | continue-from (match-beginning 0) |
33306645 | 456 | is-percent (match-beginning 2) |
47ffc456 CD |
457 | lim (cond |
458 | ((org-on-heading-p) (outline-next-heading) (point)) | |
459 | ((org-at-item-p) (org-end-of-item) (point)) | |
460 | (t nil)) | |
33306645 CD |
461 | c-on 0 |
462 | c-off 0) | |
47ffc456 | 463 | (when lim |
33306645 CD |
464 | ;; find first checkbox for this cookie and gather |
465 | ;; statistics from all that are at this indentation level | |
466 | (goto-char startsearch) | |
467 | (if (re-search-forward re-box lim t) | |
468 | (progn | |
469 | (org-beginning-of-item) | |
470 | (setq curr-ind (org-get-indentation)) | |
471 | (setq next-ind curr-ind) | |
c8d0cf5c CD |
472 | (while (and (bolp) (org-at-item-p) |
473 | (if recursive | |
474 | (<= curr-ind next-ind) | |
475 | (= curr-ind next-ind))) | |
33306645 CD |
476 | (save-excursion (end-of-line) (setq eline (point))) |
477 | (if (re-search-forward re-box eline t) | |
47ffc456 CD |
478 | (if (member (match-string 2) '("[ ]" "[-]")) |
479 | (setq c-off (1+ c-off)) | |
c8d0cf5c CD |
480 | (setq c-on (1+ c-on)))) |
481 | (if (not recursive) | |
482 | (org-end-of-item) | |
483 | (end-of-line) | |
484 | (when (re-search-forward org-list-beginning-re lim t) | |
485 | (beginning-of-line))) | |
486 | (setq next-ind (org-get-indentation))))) | |
47ffc456 | 487 | (goto-char continue-from) |
33306645 | 488 | ;; update cookie |
47ffc456 CD |
489 | (when end-cookie |
490 | (delete-region beg-cookie end-cookie) | |
491 | (goto-char beg-cookie) | |
492 | (insert | |
493 | (if is-percent | |
494 | (format "[%d%%]" (/ (* 100 c-on) (max 1 (+ c-on c-off)))) | |
495 | (format "[%d/%d]" c-on (+ c-on c-off))))) | |
33306645 CD |
496 | ;; update items checkbox if it has one |
497 | (when (org-at-item-p) | |
498 | (org-beginning-of-item) | |
499 | (when (and (> (+ c-on c-off) 0) | |
47ffc456 | 500 | (re-search-forward re-box (point-at-eol) t)) |
33306645 CD |
501 | (setq beg-cookie (match-beginning 2) |
502 | end-cookie (match-end 2)) | |
503 | (delete-region beg-cookie end-cookie) | |
504 | (goto-char beg-cookie) | |
505 | (cond ((= c-off 0) (insert "[X]")) | |
506 | ((= c-on 0) (insert "[ ]")) | |
507 | (t (insert "[-]"))) | |
508 | ))) | |
47ffc456 CD |
509 | (goto-char continue-from)) |
510 | (when (interactive-p) | |
33306645 | 511 | (message "Checkbox statistics updated %s (%d places)" |
47ffc456 CD |
512 | (if all "in entire file" "in current outline entry") cstat))))) |
513 | ||
514 | (defun org-get-checkbox-statistics-face () | |
515 | "Select the face for checkbox statistics. | |
516 | The face will be `org-done' when all relevant boxes are checked. Otherwise | |
517 | it will be `org-todo'." | |
518 | (if (match-end 1) | |
c8d0cf5c CD |
519 | (if (equal (match-string 1) "100%") |
520 | 'org-checkbox-statistics-done | |
521 | 'org-checkbox-statistics-todo) | |
47ffc456 CD |
522 | (if (and (> (match-end 2) (match-beginning 2)) |
523 | (equal (match-string 2) (match-string 3))) | |
c8d0cf5c CD |
524 | 'org-checkbox-statistics-done |
525 | 'org-checkbox-statistics-todo))) | |
47ffc456 CD |
526 | |
527 | (defun org-beginning-of-item () | |
528 | "Go to the beginning of the current hand-formatted item. | |
529 | If the cursor is not in an item, throw an error." | |
530 | (interactive) | |
531 | (let ((pos (point)) | |
532 | (limit (save-excursion | |
533 | (condition-case nil | |
534 | (progn | |
535 | (org-back-to-heading) | |
536 | (beginning-of-line 2) (point)) | |
537 | (error (point-min))))) | |
538 | (ind-empty (if org-empty-line-terminates-plain-lists 0 10000)) | |
539 | ind ind1) | |
540 | (if (org-at-item-p) | |
541 | (beginning-of-line 1) | |
542 | (beginning-of-line 1) | |
543 | (skip-chars-forward " \t") | |
544 | (setq ind (current-column)) | |
545 | (if (catch 'exit | |
546 | (while t | |
547 | (beginning-of-line 0) | |
548 | (if (or (bobp) (< (point) limit)) (throw 'exit nil)) | |
549 | ||
550 | (if (looking-at "[ \t]*$") | |
551 | (setq ind1 ind-empty) | |
552 | (skip-chars-forward " \t") | |
553 | (setq ind1 (current-column))) | |
554 | (if (< ind1 ind) | |
555 | (progn (beginning-of-line 1) (throw 'exit (org-at-item-p)))))) | |
556 | nil | |
557 | (goto-char pos) | |
558 | (error "Not in an item"))))) | |
559 | ||
560 | (defun org-end-of-item () | |
561 | "Go to the end of the current hand-formatted item. | |
562 | If the cursor is not in an item, throw an error." | |
563 | (interactive) | |
564 | (let* ((pos (point)) | |
565 | ind1 | |
566 | (ind-empty (if org-empty-line-terminates-plain-lists 0 10000)) | |
567 | (limit (save-excursion (outline-next-heading) (point))) | |
568 | (ind (save-excursion | |
569 | (org-beginning-of-item) | |
570 | (skip-chars-forward " \t") | |
571 | (current-column))) | |
572 | (end (catch 'exit | |
573 | (while t | |
574 | (beginning-of-line 2) | |
575 | (if (eobp) (throw 'exit (point))) | |
576 | (if (>= (point) limit) (throw 'exit (point-at-bol))) | |
577 | (if (looking-at "[ \t]*$") | |
578 | (setq ind1 ind-empty) | |
579 | (skip-chars-forward " \t") | |
580 | (setq ind1 (current-column))) | |
581 | (if (<= ind1 ind) | |
582 | (throw 'exit (point-at-bol))))))) | |
583 | (if end | |
584 | (goto-char end) | |
585 | (goto-char pos) | |
586 | (error "Not in an item")))) | |
587 | ||
588 | (defun org-next-item () | |
589 | "Move to the beginning of the next item in the current plain list. | |
590 | Error if not at a plain list, or if this is the last item in the list." | |
591 | (interactive) | |
592 | (let (ind ind1 (pos (point))) | |
593 | (org-beginning-of-item) | |
594 | (setq ind (org-get-indentation)) | |
595 | (org-end-of-item) | |
596 | (setq ind1 (org-get-indentation)) | |
597 | (unless (and (org-at-item-p) (= ind ind1)) | |
598 | (goto-char pos) | |
599 | (error "On last item")))) | |
600 | ||
601 | (defun org-previous-item () | |
602 | "Move to the beginning of the previous item in the current plain list. | |
603 | Error if not at a plain list, or if this is the first item in the list." | |
604 | (interactive) | |
605 | (let (beg ind ind1 (pos (point))) | |
606 | (org-beginning-of-item) | |
607 | (setq beg (point)) | |
608 | (setq ind (org-get-indentation)) | |
609 | (goto-char beg) | |
610 | (catch 'exit | |
611 | (while t | |
612 | (beginning-of-line 0) | |
613 | (if (looking-at "[ \t]*$") | |
614 | nil | |
615 | (if (<= (setq ind1 (org-get-indentation)) ind) | |
616 | (throw 'exit t))))) | |
617 | (condition-case nil | |
618 | (if (or (not (org-at-item-p)) | |
619 | (< ind1 (1- ind))) | |
620 | (error "") | |
621 | (org-beginning-of-item)) | |
622 | (error (goto-char pos) | |
623 | (error "On first item"))))) | |
624 | ||
625 | (defun org-first-list-item-p () | |
c8d0cf5c | 626 | "Is this heading the first item in a plain list?" |
47ffc456 CD |
627 | (unless (org-at-item-p) |
628 | (error "Not at a plain list item")) | |
c8d0cf5c CD |
629 | (save-excursion |
630 | (org-beginning-of-item) | |
631 | (= (point) (save-excursion (org-beginning-of-item-list))))) | |
47ffc456 CD |
632 | |
633 | (defun org-move-item-down () | |
634 | "Move the plain list item at point down, i.e. swap with following item. | |
635 | Subitems (items with larger indentation) are considered part of the item, | |
636 | so this really moves item trees." | |
637 | (interactive) | |
638 | (let ((col (current-column)) | |
639 | (pos (point)) | |
640 | beg beg0 end end0 ind ind1 txt ne-end ne-beg) | |
641 | (org-beginning-of-item) | |
642 | (setq beg0 (point)) | |
643 | (save-excursion | |
644 | (setq ne-beg (org-back-over-empty-lines)) | |
645 | (setq beg (point))) | |
646 | (goto-char beg0) | |
647 | (setq ind (org-get-indentation)) | |
648 | (org-end-of-item) | |
649 | (setq end0 (point)) | |
650 | (setq ind1 (org-get-indentation)) | |
651 | (setq ne-end (org-back-over-empty-lines)) | |
652 | (setq end (point)) | |
653 | (goto-char beg0) | |
654 | (when (and (org-first-list-item-p) (< ne-end ne-beg)) | |
655 | ;; include less whitespace | |
656 | (save-excursion | |
657 | (goto-char beg) | |
658 | (forward-line (- ne-beg ne-end)) | |
659 | (setq beg (point)))) | |
660 | (goto-char end0) | |
661 | (if (and (org-at-item-p) (= ind ind1)) | |
662 | (progn | |
663 | (org-end-of-item) | |
664 | (org-back-over-empty-lines) | |
665 | (setq txt (buffer-substring beg end)) | |
666 | (save-excursion | |
667 | (delete-region beg end)) | |
668 | (setq pos (point)) | |
669 | (insert txt) | |
670 | (goto-char pos) (org-skip-whitespace) | |
671 | (org-maybe-renumber-ordered-list) | |
672 | (move-to-column col)) | |
673 | (goto-char pos) | |
674 | (move-to-column col) | |
675 | (error "Cannot move this item further down")))) | |
676 | ||
677 | (defun org-move-item-up (arg) | |
678 | "Move the plain list item at point up, i.e. swap with previous item. | |
679 | Subitems (items with larger indentation) are considered part of the item, | |
680 | so this really moves item trees." | |
681 | (interactive "p") | |
682 | (let ((col (current-column)) (pos (point)) | |
683 | beg beg0 end ind ind1 txt | |
684 | ne-beg ne-ins ins-end) | |
685 | (org-beginning-of-item) | |
686 | (setq beg0 (point)) | |
687 | (setq ind (org-get-indentation)) | |
688 | (save-excursion | |
689 | (setq ne-beg (org-back-over-empty-lines)) | |
690 | (setq beg (point))) | |
691 | (goto-char beg0) | |
692 | (org-end-of-item) | |
693 | (org-back-over-empty-lines) | |
694 | (setq end (point)) | |
695 | (goto-char beg0) | |
696 | (catch 'exit | |
697 | (while t | |
698 | (beginning-of-line 0) | |
699 | (if (looking-at "[ \t]*$") | |
700 | (if org-empty-line-terminates-plain-lists | |
701 | (progn | |
702 | (goto-char pos) | |
703 | (error "Cannot move this item further up")) | |
704 | nil) | |
705 | (if (<= (setq ind1 (org-get-indentation)) ind) | |
706 | (throw 'exit t))))) | |
707 | (condition-case nil | |
708 | (org-beginning-of-item) | |
709 | (error (goto-char beg0) | |
710 | (move-to-column col) | |
711 | (error "Cannot move this item further up"))) | |
712 | (setq ind1 (org-get-indentation)) | |
713 | (if (and (org-at-item-p) (= ind ind1)) | |
714 | (progn | |
715 | (setq ne-ins (org-back-over-empty-lines)) | |
716 | (setq txt (buffer-substring beg end)) | |
717 | (save-excursion | |
718 | (delete-region beg end)) | |
719 | (setq pos (point)) | |
720 | (insert txt) | |
721 | (setq ins-end (point)) | |
722 | (goto-char pos) (org-skip-whitespace) | |
723 | ||
724 | (when (and (org-first-list-item-p) (> ne-ins ne-beg)) | |
725 | ;; Move whitespace back to beginning | |
726 | (save-excursion | |
727 | (goto-char ins-end) | |
728 | (let ((kill-whole-line t)) | |
729 | (kill-line (- ne-ins ne-beg)) (point))) | |
730 | (insert (make-string (- ne-ins ne-beg) ?\n))) | |
731 | ||
732 | (org-maybe-renumber-ordered-list) | |
733 | (move-to-column col)) | |
734 | (goto-char pos) | |
735 | (move-to-column col) | |
736 | (error "Cannot move this item further up")))) | |
737 | ||
738 | (defun org-maybe-renumber-ordered-list () | |
739 | "Renumber the ordered list at point if setup allows it. | |
740 | This tests the user option `org-auto-renumber-ordered-lists' before | |
741 | doing the renumbering." | |
742 | (interactive) | |
743 | (when (and org-auto-renumber-ordered-lists | |
744 | (org-at-item-p)) | |
745 | (if (match-beginning 3) | |
746 | (org-renumber-ordered-list 1) | |
747 | (org-fix-bullet-type)))) | |
748 | ||
749 | (defun org-maybe-renumber-ordered-list-safe () | |
750 | (condition-case nil | |
751 | (save-excursion | |
752 | (org-maybe-renumber-ordered-list)) | |
753 | (error nil))) | |
754 | ||
755 | (defun org-cycle-list-bullet (&optional which) | |
756 | "Cycle through the different itemize/enumerate bullets. | |
757 | This cycle the entire list level through the sequence: | |
758 | ||
33306645 | 759 | `-' -> `+' -> `*' -> `1.' -> `1)' |
47ffc456 CD |
760 | |
761 | If WHICH is a string, use that as the new bullet. If WHICH is an integer, | |
33306645 | 762 | 0 means `-', 1 means `+' etc." |
47ffc456 CD |
763 | (interactive "P") |
764 | (org-preserve-lc | |
765 | (org-beginning-of-item-list) | |
766 | (org-at-item-p) | |
767 | (beginning-of-line 1) | |
768 | (let ((current (match-string 0)) | |
769 | (prevp (eq which 'previous)) | |
ce4fdcb9 | 770 | new old) |
47ffc456 CD |
771 | (setq new (cond |
772 | ((and (numberp which) | |
773 | (nth (1- which) '("-" "+" "*" "1." "1)")))) | |
774 | ((string-match "-" current) (if prevp "1)" "+")) | |
775 | ((string-match "\\+" current) | |
776 | (if prevp "-" (if (looking-at "\\S-") "1." "*"))) | |
777 | ((string-match "\\*" current) (if prevp "+" "1.")) | |
ce4fdcb9 CD |
778 | ((string-match "\\." current) |
779 | (if prevp (if (looking-at "\\S-") "+" "*") "1)")) | |
47ffc456 CD |
780 | ((string-match ")" current) (if prevp "1." "-")) |
781 | (t (error "This should not happen")))) | |
ce4fdcb9 CD |
782 | (and (looking-at "\\([ \t]*\\)\\(\\S-+\\)") |
783 | (setq old (match-string 2)) | |
784 | (replace-match (concat "\\1" new))) | |
785 | (org-shift-item-indentation (- (length new) (length old))) | |
47ffc456 CD |
786 | (org-fix-bullet-type) |
787 | (org-maybe-renumber-ordered-list)))) | |
788 | ||
789 | (defun org-get-string-indentation (s) | |
790 | "What indentation has S due to SPACE and TAB at the beginning of the string?" | |
791 | (let ((n -1) (i 0) (w tab-width) c) | |
792 | (catch 'exit | |
793 | (while (< (setq n (1+ n)) (length s)) | |
794 | (setq c (aref s n)) | |
795 | (cond ((= c ?\ ) (setq i (1+ i))) | |
796 | ((= c ?\t) (setq i (* (/ (+ w i) w) w))) | |
797 | (t (throw 'exit t))))) | |
798 | i)) | |
799 | ||
800 | (defun org-renumber-ordered-list (arg) | |
801 | "Renumber an ordered plain list. | |
802 | Cursor needs to be in the first line of an item, the line that starts | |
803 | with something like \"1.\" or \"2)\"." | |
804 | (interactive "p") | |
805 | (unless (and (org-at-item-p) | |
806 | (match-beginning 3)) | |
807 | (error "This is not an ordered list")) | |
808 | (let ((line (org-current-line)) | |
809 | (col (current-column)) | |
810 | (ind (org-get-string-indentation | |
811 | (buffer-substring (point-at-bol) (match-beginning 3)))) | |
812 | ;; (term (substring (match-string 3) -1)) | |
813 | ind1 (n (1- arg)) | |
ce4fdcb9 | 814 | fmt bobp old new) |
47ffc456 CD |
815 | ;; find where this list begins |
816 | (org-beginning-of-item-list) | |
817 | (setq bobp (bobp)) | |
818 | (looking-at "[ \t]*[0-9]+\\([.)]\\)") | |
c8d0cf5c | 819 | (setq fmt (concat "%d" (or (match-string 1) "."))) |
47ffc456 CD |
820 | (beginning-of-line 0) |
821 | ;; walk forward and replace these numbers | |
822 | (catch 'exit | |
823 | (while t | |
824 | (catch 'next | |
825 | (if bobp (setq bobp nil) (beginning-of-line 2)) | |
826 | (if (eobp) (throw 'exit nil)) | |
827 | (if (looking-at "[ \t]*$") (throw 'next nil)) | |
828 | (skip-chars-forward " \t") (setq ind1 (current-column)) | |
829 | (if (> ind1 ind) (throw 'next t)) | |
830 | (if (< ind1 ind) (throw 'exit t)) | |
831 | (if (not (org-at-item-p)) (throw 'exit nil)) | |
ce4fdcb9 | 832 | (setq old (match-string 2)) |
47ffc456 CD |
833 | (delete-region (match-beginning 2) (match-end 2)) |
834 | (goto-char (match-beginning 2)) | |
ce4fdcb9 CD |
835 | (insert (setq new (format fmt (setq n (1+ n))))) |
836 | (org-shift-item-indentation (- (length new) (length old)))))) | |
47ffc456 CD |
837 | (goto-line line) |
838 | (org-move-to-column col))) | |
839 | ||
c8d0cf5c | 840 | (defun org-fix-bullet-type (&optional force-bullet) |
ce4fdcb9 CD |
841 | "Make sure all items in this list have the same bullet as the first item. |
842 | Also, fix the indentation." | |
47ffc456 CD |
843 | (interactive) |
844 | (unless (org-at-item-p) (error "This is not a list")) | |
845 | (let ((line (org-current-line)) | |
846 | (col (current-column)) | |
847 | (ind (current-indentation)) | |
ce4fdcb9 | 848 | ind1 bullet oldbullet) |
47ffc456 CD |
849 | ;; find where this list begins |
850 | (org-beginning-of-item-list) | |
851 | (beginning-of-line 1) | |
852 | ;; find out what the bullet type is | |
853 | (looking-at "[ \t]*\\(\\S-+\\)") | |
c8d0cf5c | 854 | (setq bullet (concat (or force-bullet (match-string 1)) " ")) |
ce4fdcb9 CD |
855 | (if (and org-list-two-spaces-after-bullet-regexp |
856 | (string-match org-list-two-spaces-after-bullet-regexp bullet)) | |
857 | (setq bullet (concat bullet " "))) | |
47ffc456 CD |
858 | ;; walk forward and replace these numbers |
859 | (beginning-of-line 0) | |
860 | (catch 'exit | |
861 | (while t | |
862 | (catch 'next | |
863 | (beginning-of-line 2) | |
864 | (if (eobp) (throw 'exit nil)) | |
865 | (if (looking-at "[ \t]*$") (throw 'next nil)) | |
866 | (skip-chars-forward " \t") (setq ind1 (current-column)) | |
867 | (if (> ind1 ind) (throw 'next t)) | |
868 | (if (< ind1 ind) (throw 'exit t)) | |
869 | (if (not (org-at-item-p)) (throw 'exit nil)) | |
870 | (skip-chars-forward " \t") | |
ce4fdcb9 CD |
871 | (looking-at "\\S-+ *") |
872 | (setq oldbullet (match-string 0)) | |
c8d0cf5c | 873 | (unless (equal bullet oldbullet) (replace-match bullet)) |
ce4fdcb9 | 874 | (org-shift-item-indentation (- (length bullet) (length oldbullet)))))) |
47ffc456 CD |
875 | (goto-line line) |
876 | (org-move-to-column col) | |
877 | (if (string-match "[0-9]" bullet) | |
878 | (org-renumber-ordered-list 1)))) | |
879 | ||
ce4fdcb9 CD |
880 | (defun org-shift-item-indentation (delta) |
881 | "Shift the indentation in current item by DELTA." | |
882 | (save-excursion | |
883 | (let ((beg (point-at-bol)) | |
884 | (end (progn (org-end-of-item) (point))) | |
885 | i) | |
886 | (goto-char end) | |
887 | (beginning-of-line 0) | |
888 | (while (> (point) beg) | |
889 | (when (looking-at "[ \t]*\\S-") | |
890 | ;; this is not an empty line | |
891 | (setq i (org-get-indentation)) | |
892 | (if (and (> i 0) (> (setq i (+ i delta)) 0)) | |
893 | (indent-line-to i))) | |
894 | (beginning-of-line 0))))) | |
895 | ||
47ffc456 CD |
896 | (defun org-beginning-of-item-list () |
897 | "Go to the beginning of the current item list. | |
898 | I.e. to the first item in this list." | |
899 | (interactive) | |
900 | (org-beginning-of-item) | |
901 | (let ((pos (point-at-bol)) | |
33306645 | 902 | (ind (org-get-indentation)) |
47ffc456 CD |
903 | ind1) |
904 | ;; find where this list begins | |
905 | (catch 'exit | |
906 | (while t | |
907 | (catch 'next | |
908 | (beginning-of-line 0) | |
909 | (if (looking-at "[ \t]*$") | |
910 | (throw (if (bobp) 'exit 'next) t)) | |
911 | (skip-chars-forward " \t") (setq ind1 (current-column)) | |
912 | (if (or (< ind1 ind) | |
913 | (and (= ind1 ind) | |
914 | (not (org-at-item-p))) | |
915 | (and (= (point-at-bol) (point-min)) | |
916 | (setq pos (point-min)))) | |
917 | (throw 'exit t) | |
918 | (when (org-at-item-p) (setq pos (point-at-bol))))))) | |
919 | (goto-char pos))) | |
920 | ||
47ffc456 CD |
921 | (defun org-end-of-item-list () |
922 | "Go to the end of the current item list. | |
923 | I.e. to the text after the last item." | |
924 | (interactive) | |
925 | (org-beginning-of-item) | |
926 | (let ((pos (point-at-bol)) | |
33306645 | 927 | (ind (org-get-indentation)) |
47ffc456 CD |
928 | ind1) |
929 | ;; find where this list begins | |
930 | (catch 'exit | |
931 | (while t | |
932 | (catch 'next | |
933 | (beginning-of-line 2) | |
934 | (if (looking-at "[ \t]*$") | |
c8d0cf5c CD |
935 | (if (eobp) |
936 | (progn (setq pos (point)) (throw 'exit t)) | |
937 | (throw 'next t))) | |
47ffc456 CD |
938 | (skip-chars-forward " \t") (setq ind1 (current-column)) |
939 | (if (or (< ind1 ind) | |
940 | (and (= ind1 ind) | |
941 | (not (org-at-item-p))) | |
942 | (eobp)) | |
943 | (progn | |
944 | (setq pos (point-at-bol)) | |
945 | (throw 'exit t)))))) | |
946 | (goto-char pos))) | |
947 | ||
948 | ||
949 | (defvar org-last-indent-begin-marker (make-marker)) | |
950 | (defvar org-last-indent-end-marker (make-marker)) | |
951 | ||
952 | (defun org-outdent-item (arg) | |
953 | "Outdent a local list item." | |
954 | (interactive "p") | |
955 | (org-indent-item (- arg))) | |
956 | ||
957 | (defun org-indent-item (arg) | |
958 | "Indent a local list item." | |
959 | (interactive "p") | |
c8d0cf5c | 960 | (and (org-region-active-p) (org-cursor-to-region-beginning)) |
47ffc456 CD |
961 | (unless (org-at-item-p) |
962 | (error "Not on an item")) | |
c8d0cf5c CD |
963 | (let (beg end ind ind1 ind-bul delta ind-down ind-up firstp) |
964 | (setq firstp (org-first-list-item-p)) | |
965 | (save-excursion | |
966 | (setq end (and (org-region-active-p) (region-end))) | |
47ffc456 CD |
967 | (if (memq last-command '(org-shiftmetaright org-shiftmetaleft)) |
968 | (setq beg org-last-indent-begin-marker | |
969 | end org-last-indent-end-marker) | |
970 | (org-beginning-of-item) | |
971 | (setq beg (move-marker org-last-indent-begin-marker (point))) | |
972 | (org-end-of-item) | |
c8d0cf5c | 973 | (setq end (move-marker org-last-indent-end-marker (or end (point))))) |
47ffc456 | 974 | (goto-char beg) |
c8d0cf5c CD |
975 | (setq ind-bul (org-item-indent-positions) |
976 | ind (caar ind-bul) | |
977 | ind-down (car (nth 2 ind-bul)) | |
978 | ind-up (car (nth 1 ind-bul)) | |
47ffc456 CD |
979 | delta (if (> arg 0) |
980 | (if ind-down (- ind-down ind) 2) | |
981 | (if ind-up (- ind-up ind) -2))) | |
982 | (if (< (+ delta ind) 0) (error "Cannot outdent beyond margin")) | |
983 | (while (< (point) end) | |
984 | (beginning-of-line 1) | |
985 | (skip-chars-forward " \t") (setq ind1 (current-column)) | |
986 | (delete-region (point-at-bol) (point)) | |
987 | (or (eolp) (org-indent-to-column (+ ind1 delta))) | |
c8d0cf5c CD |
988 | (beginning-of-line 2))) |
989 | (org-fix-bullet-type | |
990 | (and (> arg 0) | |
991 | (not firstp) | |
992 | (cdr (assoc (cdr (nth 0 ind-bul)) org-list-demote-modify-bullet)))) | |
993 | (org-maybe-renumber-ordered-list-safe) | |
994 | (save-excursion | |
995 | (beginning-of-line 0) | |
996 | (condition-case nil (org-beginning-of-item) (error nil)) | |
997 | (org-maybe-renumber-ordered-list-safe)))) | |
47ffc456 CD |
998 | |
999 | (defun org-item-indent-positions () | |
1000 | "Return indentation for plain list items. | |
33306645 CD |
1001 | This returns a list with three values: The current indentation, the |
1002 | parent indentation and the indentation a child should have. | |
47ffc456 CD |
1003 | Assumes cursor in item line." |
1004 | (let* ((bolpos (point-at-bol)) | |
1005 | (ind (org-get-indentation)) | |
c8d0cf5c CD |
1006 | (bullet (org-get-bullet)) |
1007 | ind-down ind-up bullet-up bullet-down pos) | |
47ffc456 CD |
1008 | (save-excursion |
1009 | (org-beginning-of-item-list) | |
1010 | (skip-chars-backward "\n\r \t") | |
1011 | (when (org-in-item-p) | |
1012 | (org-beginning-of-item) | |
c8d0cf5c CD |
1013 | (setq ind-up (org-get-indentation)) |
1014 | (setq bullet-up (org-get-bullet)))) | |
47ffc456 CD |
1015 | (setq pos (point)) |
1016 | (save-excursion | |
1017 | (cond | |
1018 | ((and (condition-case nil (progn (org-previous-item) t) | |
1019 | (error nil)) | |
1020 | (or (forward-char 1) t) | |
1021 | (re-search-forward "^\\([ \t]*\\([-+]\\|\\([0-9]+[.)]\\)\\)\\|[ \t]+\\*\\)\\( \\|$\\)" bolpos t)) | |
c8d0cf5c CD |
1022 | (setq ind-down (org-get-indentation) |
1023 | bullet-down (org-get-bullet))) | |
47ffc456 CD |
1024 | ((and (goto-char pos) |
1025 | (org-at-item-p)) | |
1026 | (goto-char (match-end 0)) | |
1027 | (skip-chars-forward " \t") | |
c8d0cf5c CD |
1028 | (setq ind-down (current-column) |
1029 | bullet-down (org-get-bullet))))) | |
1030 | (if (and bullet-down (string-match "\\`[0-9]+\\(\\.\\|)\\)\\'" bullet-down)) | |
1031 | (setq bullet-down (concat "1" (match-string 1 bullet-down)))) | |
1032 | (if (and bullet-up (string-match "\\`[0-9]+\\(\\.\\|)\\)\\'" bullet-up)) | |
1033 | (setq bullet-up (concat "1" (match-string 1 bullet-up)))) | |
1034 | (if (and bullet (string-match "\\`[0-9]+\\(\\.\\|)\\)\\'" bullet)) | |
1035 | (setq bullet (concat "1" (match-string 1 bullet)))) | |
1036 | (list (cons ind bullet) | |
1037 | (cons ind-up bullet-up) | |
1038 | (cons ind-down bullet-down)))) | |
1039 | ||
1040 | (defun org-get-bullet () | |
1041 | (save-excursion | |
1042 | (goto-char (point-at-bol)) | |
1043 | (and (looking-at | |
1044 | "^\\([ \t]*\\([-+]\\|\\([0-9]+[.)]\\)\\)\\|[ \t]+\\(\\*\\)\\)\\( \\|$\\)") | |
1045 | (or (match-string 2) (match-string 4))))) | |
47ffc456 CD |
1046 | |
1047 | ;;; Send and receive lists | |
1048 | ||
1049 | (defun org-list-parse-list (&optional delete) | |
1050 | "Parse the list at point and maybe DELETE it. | |
1051 | Return a list containing first level items as strings and | |
1052 | sublevels as a list of strings." | |
1053 | (let* ((item-beginning (org-list-item-beginning)) | |
33306645 CD |
1054 | (start (car item-beginning)) |
1055 | (end (org-list-end (cdr item-beginning))) | |
1056 | output itemsep ltype) | |
47ffc456 CD |
1057 | (while (re-search-forward org-list-beginning-re end t) |
1058 | (goto-char (match-beginning 3)) | |
1059 | (save-match-data | |
33306645 CD |
1060 | (cond ((string-match "[0-9]" (match-string 2)) |
1061 | (setq itemsep "[0-9]+\\(?:\\.\\|)\\)" | |
1062 | ltype 'ordered)) | |
1063 | ((string-match "^.*::" (match-string 0)) | |
1064 | (setq itemsep "[-+]" ltype 'descriptive)) | |
1065 | (t (setq itemsep "[-+]" ltype 'unordered)))) | |
47ffc456 CD |
1066 | (let* ((indent1 (match-string 1)) |
1067 | (nextitem (save-excursion | |
1068 | (save-match-data | |
1069 | (or (and (re-search-forward | |
1070 | (concat "^" indent1 itemsep " *?") end t) | |
1071 | (match-beginning 0)) end)))) | |
1072 | (item (buffer-substring | |
1073 | (point) | |
1074 | (or (and (re-search-forward | |
1075 | org-list-beginning-re end t) | |
1076 | (goto-char (match-beginning 0))) | |
1077 | (goto-char end)))) | |
1078 | (nextindent (match-string 1)) | |
1079 | (item (org-trim item)) | |
0bd48b37 CD |
1080 | (item (if (string-match "^\\[\\([xX ]\\)\\]" item) |
1081 | (replace-match (if (equal (match-string 1 item) " ") | |
1082 | "[CBOFF]" | |
1083 | "[CBON]") | |
1084 | t nil item) | |
1085 | item))) | |
47ffc456 CD |
1086 | (push item output) |
1087 | (when (> (length nextindent) | |
1088 | (length indent1)) | |
1089 | (narrow-to-region (point) nextitem) | |
1090 | (push (org-list-parse-list) output) | |
1091 | (widen)))) | |
1092 | (when delete (delete-region start end)) | |
1093 | (setq output (nreverse output)) | |
1094 | (push ltype output))) | |
1095 | ||
1096 | (defun org-list-item-beginning () | |
1097 | "Find the beginning of the list item. | |
1098 | Return a cons which car is the beginning position of the item and | |
1099 | cdr is the indentation string." | |
1100 | (save-excursion | |
1101 | (if (not (or (looking-at org-list-beginning-re) | |
1102 | (re-search-backward | |
1103 | org-list-beginning-re nil t))) | |
1104 | (progn (goto-char (point-min)) (point)) | |
1105 | (cons (match-beginning 0) (match-string 1))))) | |
1106 | ||
c8d0cf5c CD |
1107 | (defun org-list-goto-true-beginning () |
1108 | "Go to the beginning of the list at point." | |
1109 | (beginning-of-line 1) | |
1110 | (while (looking-at org-list-beginning-re) | |
1111 | (beginning-of-line 0)) | |
1112 | (progn | |
1113 | (re-search-forward org-list-beginning-re nil t) | |
1114 | (goto-char (match-beginning 0)))) | |
1115 | ||
1116 | (defun org-list-make-subtree () | |
1117 | "Convert the plain list at point into a subtree." | |
1118 | (interactive) | |
1119 | (org-list-goto-true-beginning) | |
1120 | (let ((list (org-list-parse-list t)) nstars) | |
1121 | (save-excursion | |
1122 | (if (condition-case nil | |
1123 | (org-back-to-heading) | |
1124 | (error nil)) | |
1125 | (progn (re-search-forward org-complex-heading-regexp nil t) | |
1126 | (setq nstars (length (match-string 1)))) | |
1127 | (setq nstars 0))) | |
1128 | (org-list-make-subtrees list (1+ nstars)))) | |
1129 | ||
1130 | (defun org-list-make-subtrees (list level) | |
1131 | "Convert LIST into subtrees starting at LEVEL." | |
1132 | (if (symbolp (car list)) | |
1133 | (org-list-make-subtrees (cdr list) level) | |
1134 | (mapcar (lambda (item) | |
1135 | (if (stringp item) | |
1136 | (insert (make-string | |
1137 | (if org-odd-levels-only | |
1138 | (1- (* 2 level)) level) ?*) " " item "\n") | |
1139 | (org-list-make-subtrees item (1+ level)))) | |
1140 | list))) | |
1141 | ||
47ffc456 CD |
1142 | (defun org-list-end (indent) |
1143 | "Return the position of the end of the list. | |
c8d0cf5c | 1144 | INDENT is the indentation of the list, as a string." |
47ffc456 CD |
1145 | (save-excursion |
1146 | (catch 'exit | |
1147 | (while (or (looking-at org-list-beginning-re) | |
c8d0cf5c CD |
1148 | (looking-at (concat "^" indent "[ \t]+\\|^$")) |
1149 | (> (or (get-text-property (point) 'original-indentation) -1) | |
1150 | (length indent))) | |
47ffc456 CD |
1151 | (if (eq (point) (point-max)) |
1152 | (throw 'exit (point-max))) | |
c8d0cf5c CD |
1153 | (forward-line 1))) |
1154 | (point))) | |
47ffc456 CD |
1155 | |
1156 | (defun org-list-insert-radio-list () | |
1157 | "Insert a radio list template appropriate for this major mode." | |
1158 | (interactive) | |
1159 | (let* ((e (assq major-mode org-list-radio-list-templates)) | |
1160 | (txt (nth 1 e)) | |
1161 | name pos) | |
1162 | (unless e (error "No radio list setup defined for %s" major-mode)) | |
1163 | (setq name (read-string "List name: ")) | |
1164 | (while (string-match "%n" txt) | |
1165 | (setq txt (replace-match name t t txt))) | |
1166 | (or (bolp) (insert "\n")) | |
1167 | (setq pos (point)) | |
1168 | (insert txt) | |
1169 | (goto-char pos))) | |
1170 | ||
1171 | (defun org-list-send-list (&optional maybe) | |
1172 | "Send a tranformed version of this list to the receiver position. | |
1173 | With argument MAYBE, fail quietly if no transformation is defined for | |
1174 | this list." | |
1175 | (interactive) | |
1176 | (catch 'exit | |
1177 | (unless (org-at-item-p) (error "Not at a list")) | |
1178 | (save-excursion | |
c8d0cf5c | 1179 | (org-list-goto-true-beginning) |
47ffc456 CD |
1180 | (beginning-of-line 0) |
1181 | (unless (looking-at "#\\+ORGLST: *SEND +\\([a-zA-Z0-9_]+\\) +\\([^ \t\r\n]+\\)\\( +.*\\)?") | |
1182 | (if maybe | |
1183 | (throw 'exit nil) | |
1184 | (error "Don't know how to transform this list")))) | |
1185 | (let* ((name (match-string 1)) | |
33306645 | 1186 | (item-beginning (org-list-item-beginning)) |
47ffc456 CD |
1187 | (transform (intern (match-string 2))) |
1188 | (txt (buffer-substring-no-properties | |
33306645 | 1189 | (car item-beginning) |
47ffc456 CD |
1190 | (org-list-end (cdr item-beginning)))) |
1191 | (list (org-list-parse-list)) | |
33306645 | 1192 | beg) |
47ffc456 CD |
1193 | (unless (fboundp transform) |
1194 | (error "No such transformation function %s" transform)) | |
1195 | (setq txt (funcall transform list)) | |
1196 | ;; Find the insertion place | |
1197 | (save-excursion | |
1198 | (goto-char (point-min)) | |
1199 | (unless (re-search-forward | |
1200 | (concat "BEGIN RECEIVE ORGLST +" name "\\([ \t]\\|$\\)") nil t) | |
1201 | (error "Don't know where to insert translated list")) | |
1202 | (goto-char (match-beginning 0)) | |
1203 | (beginning-of-line 2) | |
1204 | (setq beg (point)) | |
1205 | (unless (re-search-forward (concat "END RECEIVE ORGLST +" name) nil t) | |
1206 | (error "Cannot find end of insertion region")) | |
1207 | (beginning-of-line 1) | |
1208 | (delete-region beg (point)) | |
1209 | (goto-char beg) | |
1210 | (insert txt "\n")) | |
1211 | (message "List converted and installed at receiver location")))) | |
1212 | ||
1213 | (defun org-list-to-generic (list params) | |
1214 | "Convert a LIST parsed through `org-list-parse-list' to other formats. | |
1215 | ||
1216 | Valid parameters PARAMS are | |
1217 | ||
33306645 CD |
1218 | :ustart String to start an unordered list |
1219 | :uend String to end an unordered list | |
47ffc456 | 1220 | |
33306645 CD |
1221 | :ostart String to start an ordered list |
1222 | :oend String to end an ordered list | |
47ffc456 | 1223 | |
33306645 CD |
1224 | :dstart String to start a descriptive list |
1225 | :dend String to end a descriptive list | |
47ffc456 | 1226 | :dtstart String to start a descriptive term |
33306645 | 1227 | :dtend String to end a descriptive term |
47ffc456 | 1228 | :ddstart String to start a description |
33306645 | 1229 | :ddend String to end a description |
47ffc456 | 1230 | |
33306645 CD |
1231 | :splice When set to t, return only list body lines, don't wrap |
1232 | them into :[u/o]start and :[u/o]end. Default is nil. | |
47ffc456 | 1233 | |
33306645 CD |
1234 | :istart String to start a list item |
1235 | :iend String to end a list item | |
1236 | :isep String to separate items | |
0bd48b37 CD |
1237 | :lsep String to separate sublists |
1238 | ||
1239 | :cboff String to insert for an unchecked checkbox | |
1240 | :cbon String to insert for a checked checkbox" | |
47ffc456 CD |
1241 | (interactive) |
1242 | (let* ((p params) sublist | |
1243 | (splicep (plist-get p :splice)) | |
1244 | (ostart (plist-get p :ostart)) | |
33306645 | 1245 | (oend (plist-get p :oend)) |
47ffc456 | 1246 | (ustart (plist-get p :ustart)) |
33306645 | 1247 | (uend (plist-get p :uend)) |
47ffc456 | 1248 | (dstart (plist-get p :dstart)) |
33306645 | 1249 | (dend (plist-get p :dend)) |
47ffc456 | 1250 | (dtstart (plist-get p :dtstart)) |
33306645 | 1251 | (dtend (plist-get p :dtend)) |
47ffc456 | 1252 | (ddstart (plist-get p :ddstart)) |
33306645 | 1253 | (ddend (plist-get p :ddend)) |
47ffc456 | 1254 | (istart (plist-get p :istart)) |
33306645 CD |
1255 | (iend (plist-get p :iend)) |
1256 | (isep (plist-get p :isep)) | |
0bd48b37 CD |
1257 | (lsep (plist-get p :lsep)) |
1258 | (cbon (plist-get p :cbon)) | |
1259 | (cboff (plist-get p :cboff))) | |
47ffc456 CD |
1260 | (let ((wrapper |
1261 | (cond ((eq (car list) 'ordered) | |
1262 | (concat ostart "\n%s" oend "\n")) | |
1263 | ((eq (car list) 'unordered) | |
1264 | (concat ustart "\n%s" uend "\n")) | |
1265 | ((eq (car list) 'descriptive) | |
1266 | (concat dstart "\n%s" dend "\n")))) | |
1267 | rtn term defstart defend) | |
1268 | (while (setq sublist (pop list)) | |
1269 | (cond ((symbolp sublist) nil) | |
1270 | ((stringp sublist) | |
33306645 CD |
1271 | (when (string-match "^\\(.*\\) ::" sublist) |
1272 | (setq term (org-trim (format (concat dtstart "%s" dtend) | |
1273 | (match-string 1 sublist)))) | |
1274 | (setq sublist (substring sublist (1+ (length term))))) | |
0bd48b37 CD |
1275 | (if (string-match "\\[CBON\\]" sublist) |
1276 | (setq sublist (replace-match cbon t t sublist))) | |
1277 | (if (string-match "\\[CBOFF\\]" sublist) | |
1278 | (setq sublist (replace-match cboff t t sublist))) | |
33306645 CD |
1279 | (setq rtn (concat rtn istart term ddstart |
1280 | sublist ddend iend isep))) | |
1281 | (t (setq rtn (concat rtn ;; previous list | |
1282 | lsep ;; list separator | |
1283 | (org-list-to-generic sublist p) | |
1284 | lsep ;; list separator | |
1285 | ))))) | |
47ffc456 CD |
1286 | (format wrapper rtn)))) |
1287 | ||
0bd48b37 CD |
1288 | (defun org-list-to-latex (list &optional params) |
1289 | "Convert LIST into a LaTeX list. | |
1290 | LIST is as returnd by `org-list-parse-list'. PARAMS is a property list | |
1291 | with overruling parameters for `org-list-to-generic'." | |
47ffc456 | 1292 | (org-list-to-generic |
0bd48b37 CD |
1293 | list |
1294 | (org-combine-plists | |
1295 | '(:splicep nil :ostart "\\begin{enumerate}" :oend "\\end{enumerate}" | |
1296 | :ustart "\\begin{itemize}" :uend "\\end{itemize}" | |
1297 | :dstart "\\begin{description}" :dend "\\end{description}" | |
1298 | :dtstart "[" :dtend "]" | |
1299 | :ddstart "" :ddend "" | |
1300 | :istart "\\item " :iend "" | |
1301 | :isep "\n" :lsep "\n" | |
1302 | :cbon "\\texttt{[X]}" :cboff "\\texttt{[ ]}") | |
1303 | params))) | |
1304 | ||
1305 | (defun org-list-to-html (list &optional params) | |
1306 | "Convert LIST into a HTML list. | |
1307 | LIST is as returnd by `org-list-parse-list'. PARAMS is a property list | |
1308 | with overruling parameters for `org-list-to-generic'." | |
47ffc456 | 1309 | (org-list-to-generic |
0bd48b37 CD |
1310 | list |
1311 | (org-combine-plists | |
1312 | '(:splicep nil :ostart "<ol>" :oend "</ol>" | |
1313 | :ustart "<ul>" :uend "</ul>" | |
1314 | :dstart "<dl>" :dend "</dl>" | |
1315 | :dtstart "<dt>" :dtend "</dt>" | |
1316 | :ddstart "<dd>" :ddend "</dd>" | |
1317 | :istart "<li>" :iend "</li>" | |
1318 | :isep "\n" :lsep "\n" | |
1319 | :cbon "<code>[X]</code>" :cboff "<code>[ ]</code>") | |
1320 | params))) | |
1321 | ||
1322 | (defun org-list-to-texinfo (list &optional params) | |
1323 | "Convert LIST into a Texinfo list. | |
1324 | LIST is as returnd by `org-list-parse-list'. PARAMS is a property list | |
1325 | with overruling parameters for `org-list-to-generic'." | |
47ffc456 | 1326 | (org-list-to-generic |
c8d0cf5c | 1327 | list |
0bd48b37 CD |
1328 | (org-combine-plists |
1329 | '(:splicep nil :ostart "@itemize @minus" :oend "@end itemize" | |
1330 | :ustart "@enumerate" :uend "@end enumerate" | |
1331 | :dstart "@table" :dend "@end table" | |
1332 | :dtstart "@item " :dtend "\n" | |
1333 | :ddstart "" :ddend "" | |
1334 | :istart "@item\n" :iend "" | |
1335 | :isep "\n" :lsep "\n" | |
1336 | :cbon "@code{[X]}" :cboff "@code{[ ]}") | |
1337 | params))) | |
47ffc456 CD |
1338 | |
1339 | (provide 'org-list) | |
1340 | ||
3048977d | 1341 | ;; arch-tag: 73cf50c1-200f-4d1d-8a53-4e842a5b11c8 |
47ffc456 | 1342 | ;;; org-list.el ends here |