commit | author | age
|
76bbd0
|
1 |
;;; org-list.el --- Plain lists for Org -*- lexical-binding: t; -*- |
C |
2 |
;; |
|
3 |
;; Copyright (C) 2004-2018 Free Software Foundation, Inc. |
|
4 |
;; |
|
5 |
;; Author: Carsten Dominik <carsten at orgmode dot org> |
|
6 |
;; Bastien Guerry <bzg@gnu.org> |
|
7 |
;; Keywords: outlines, hypermedia, calendar, wp |
|
8 |
;; Homepage: https://orgmode.org |
|
9 |
;; |
|
10 |
;; This file is part of GNU Emacs. |
|
11 |
;; |
|
12 |
;; GNU Emacs is free software: you can redistribute it and/or modify |
|
13 |
;; it under the terms of the GNU General Public License as published by |
|
14 |
;; the Free Software Foundation, either version 3 of the License, or |
|
15 |
;; (at your option) any later version. |
|
16 |
|
|
17 |
;; GNU Emacs is distributed in the hope that it will be useful, |
|
18 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
;; GNU General Public License for more details. |
|
21 |
|
|
22 |
;; You should have received a copy of the GNU General Public License |
|
23 |
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. |
|
24 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
25 |
;; |
|
26 |
;;; Commentary: |
|
27 |
|
|
28 |
;; This file contains the code dealing with plain lists in Org mode. |
|
29 |
|
|
30 |
;; The core concept behind lists is their structure. A structure is |
|
31 |
;; a snapshot of the list, in the shape of a data tree (see |
|
32 |
;; `org-list-struct'). |
|
33 |
|
|
34 |
;; Once the list structure is stored, it is possible to make changes |
|
35 |
;; on it that will be mirrored to the real list or to get information |
|
36 |
;; about the list, using accessors and methods provided in the |
|
37 |
;; library. Most of them require the use of one or two helper |
|
38 |
;; functions, namely `org-list-parents-alist' and |
|
39 |
;; `org-list-prevs-alist'. |
|
40 |
|
|
41 |
;; Structure is eventually applied to the buffer with |
|
42 |
;; `org-list-write-struct'. This function repairs (bullets, |
|
43 |
;; indentation, checkboxes) the list in the process. It should be |
|
44 |
;; called near the end of any function working on structures. |
|
45 |
|
|
46 |
;; Thus, a function applying to lists should usually follow this |
|
47 |
;; template: |
|
48 |
|
|
49 |
;; 1. Verify point is in a list and grab item beginning (with the same |
|
50 |
;; function `org-in-item-p'). If the function requires the cursor |
|
51 |
;; to be at item's bullet, `org-at-item-p' is more selective. It |
|
52 |
;; is also possible to move point to the closest item with |
|
53 |
;; `org-list-search-backward', or `org-list-search-forward', |
|
54 |
;; applied to the function `org-item-beginning-re'. |
|
55 |
|
|
56 |
;; 2. Get list structure with `org-list-struct'. |
|
57 |
|
|
58 |
;; 3. Compute one, or both, helper functions, |
|
59 |
;; (`org-list-parents-alist', `org-list-prevs-alist') depending on |
|
60 |
;; needed accessors. |
|
61 |
|
|
62 |
;; 4. Proceed with the modifications, using methods and accessors. |
|
63 |
|
|
64 |
;; 5. Verify and apply structure to buffer, using |
|
65 |
;; `org-list-write-struct'. |
|
66 |
|
|
67 |
;; 6. If changes made to the list might have modified check-boxes, |
|
68 |
;; call `org-update-checkbox-count-maybe'. |
|
69 |
|
|
70 |
;; Computing a structure can be a costly operation on huge lists (a |
|
71 |
;; few thousand lines long). Thus, code should follow the rule: |
|
72 |
;; "collect once, use many". As a corollary, it is usually a bad idea |
|
73 |
;; to use directly an interactive function inside the code, as those, |
|
74 |
;; being independent entities, read the whole list structure another |
|
75 |
;; time. |
|
76 |
|
|
77 |
;;; Code: |
|
78 |
|
|
79 |
(require 'cl-lib) |
|
80 |
(require 'org-macs) |
|
81 |
(require 'org-compat) |
|
82 |
|
|
83 |
(defvar org-M-RET-may-split-line) |
|
84 |
(defvar org-auto-align-tags) |
|
85 |
(defvar org-blank-before-new-entry) |
|
86 |
(defvar org-clock-string) |
|
87 |
(defvar org-closed-string) |
|
88 |
(defvar org-deadline-string) |
|
89 |
(defvar org-description-max-indent) |
|
90 |
(defvar org-done-keywords) |
|
91 |
(defvar org-drawer-regexp) |
|
92 |
(defvar org-element-all-objects) |
|
93 |
(defvar org-inhibit-startup) |
|
94 |
(defvar org-odd-levels-only) |
|
95 |
(defvar org-outline-regexp-bol) |
|
96 |
(defvar org-scheduled-string) |
|
97 |
(defvar org-todo-line-regexp) |
|
98 |
(defvar org-ts-regexp) |
|
99 |
(defvar org-ts-regexp-both) |
|
100 |
|
|
101 |
(declare-function org-at-heading-p "org" (&optional invisible-ok)) |
|
102 |
(declare-function org-back-to-heading "org" (&optional invisible-ok)) |
|
103 |
(declare-function org-before-first-heading-p "org" ()) |
|
104 |
(declare-function org-combine-plists "org" (&rest plists)) |
|
105 |
(declare-function org-current-level "org" ()) |
|
106 |
(declare-function org-element-at-point "org-element" ()) |
|
107 |
(declare-function org-element-context "org-element" (&optional element)) |
|
108 |
(declare-function org-element-interpret-data "org-element" (data)) |
|
109 |
(declare-function |
|
110 |
org-element-lineage "org-element" (blob &optional types with-self)) |
|
111 |
(declare-function org-element-macro-interpreter "org-element" (macro ##)) |
|
112 |
(declare-function |
|
113 |
org-element-map "org-element" |
|
114 |
(data types fun &optional info first-match no-recursion with-affiliated)) |
|
115 |
(declare-function org-element-normalize-string "org-element" (s)) |
|
116 |
(declare-function org-element-parse-buffer "org-element" |
|
117 |
(&optional granularity visible-only)) |
|
118 |
(declare-function org-element-property "org-element" (property element)) |
|
119 |
(declare-function org-element-put-property "org-element" |
|
120 |
(element property value)) |
|
121 |
(declare-function org-element-set-element "org-element" (old new)) |
|
122 |
(declare-function org-element-type "org-element" (element)) |
|
123 |
(declare-function org-element-update-syntax "org-element" ()) |
|
124 |
(declare-function org-end-of-meta-data "org" (&optional full)) |
|
125 |
(declare-function org-entry-get "org" |
|
126 |
(pom property &optional inherit literal-nil)) |
|
127 |
(declare-function org-export-create-backend "ox" (&rest rest) t) |
|
128 |
(declare-function org-export-data-with-backend "ox" (data backend info)) |
|
129 |
(declare-function org-export-get-backend "ox" (name)) |
|
130 |
(declare-function org-export-get-environment "ox" |
|
131 |
(&optional backend subtreep ext-plist)) |
|
132 |
(declare-function org-export-get-next-element "ox" |
|
133 |
(blob info &optional n)) |
|
134 |
(declare-function org-export-with-backend "ox" |
|
135 |
(backend data &optional contents info)) |
|
136 |
(declare-function org-fix-tags-on-the-fly "org" ()) |
|
137 |
(declare-function org-get-indentation "org" (&optional line)) |
|
138 |
(declare-function org-get-todo-state "org" ()) |
|
139 |
(declare-function org-in-block-p "org" (names)) |
|
140 |
(declare-function org-in-regexp "org" (re &optional nlines visually)) |
|
141 |
(declare-function org-inlinetask-goto-beginning "org-inlinetask" ()) |
|
142 |
(declare-function org-inlinetask-goto-end "org-inlinetask" ()) |
|
143 |
(declare-function org-inlinetask-in-task-p "org-inlinetask" ()) |
|
144 |
(declare-function org-inlinetask-outline-regexp "org-inlinetask" ()) |
|
145 |
(declare-function org-level-increment "org" ()) |
|
146 |
(declare-function org-narrow-to-subtree "org" ()) |
|
147 |
(declare-function org-outline-level "org" ()) |
|
148 |
(declare-function org-previous-line-empty-p "org" ()) |
|
149 |
(declare-function org-reduced-level "org" (L)) |
|
150 |
(declare-function org-remove-indentation "org" (code &optional n)) |
|
151 |
(declare-function org-show-subtree "org" ()) |
|
152 |
(declare-function org-sort-remove-invisible "org" (S)) |
|
153 |
(declare-function org-time-string-to-seconds "org" (s)) |
|
154 |
(declare-function org-timer-hms-to-secs "org-timer" (hms)) |
|
155 |
(declare-function org-timer-item "org-timer" (&optional arg)) |
|
156 |
(declare-function org-trim "org" (s &optional keep-lead)) |
|
157 |
(declare-function org-uniquify "org" (list)) |
|
158 |
(declare-function org-invisible-p "org" (&optional pos)) |
|
159 |
(declare-function outline-flag-region "outline" (from to flag)) |
|
160 |
(declare-function outline-next-heading "outline" ()) |
|
161 |
(declare-function outline-previous-heading "outline" ()) |
|
162 |
|
|
163 |
|
|
164 |
|
|
165 |
;;; Configuration variables |
|
166 |
|
|
167 |
(defgroup org-plain-lists nil |
|
168 |
"Options concerning plain lists in Org mode." |
|
169 |
:tag "Org Plain lists" |
|
170 |
:group 'org-structure) |
|
171 |
|
|
172 |
(defcustom org-cycle-include-plain-lists t |
|
173 |
"When t, make TAB cycle visibility on plain list items. |
|
174 |
Cycling plain lists works only when the cursor is on a plain list |
|
175 |
item. When the cursor is on an outline heading, plain lists are |
|
176 |
treated as text. This is the most stable way of handling this, |
|
177 |
which is why it is the default. |
|
178 |
|
|
179 |
When this is the symbol `integrate', then integrate plain list |
|
180 |
items when cycling, as if they were children of outline headings. |
|
181 |
|
|
182 |
This setting can lead to strange effects when switching visibility |
|
183 |
to `children', because the first \"child\" in a subtree decides |
|
184 |
what children should be listed. If that first \"child\" is a |
|
185 |
plain list item with an implied large level number, all true |
|
186 |
children and grand children of the outline heading will be |
|
187 |
exposed in a children' view." |
|
188 |
:group 'org-plain-lists |
|
189 |
:group 'org-cycle |
|
190 |
:type '(choice |
|
191 |
(const :tag "Never" nil) |
|
192 |
(const :tag "With cursor in plain list (recommended)" t) |
|
193 |
(const :tag "As children of outline headings" integrate))) |
|
194 |
|
|
195 |
(defcustom org-list-demote-modify-bullet nil |
|
196 |
"Default bullet type installed when demoting an item. |
|
197 |
This is an association list, for each bullet type, this alist will point |
|
198 |
to the bullet that should be used when this item is demoted. |
|
199 |
For example, |
|
200 |
|
|
201 |
(setq org-list-demote-modify-bullet |
|
202 |
\\='((\"+\" . \"-\") (\"-\" . \"+\") (\"*\" . \"+\"))) |
|
203 |
|
|
204 |
will make |
|
205 |
|
|
206 |
+ Movies |
|
207 |
+ Silence of the Lambs |
|
208 |
+ My Cousin Vinny |
|
209 |
+ Books |
|
210 |
+ The Hunt for Red October |
|
211 |
+ The Road to Omaha |
|
212 |
|
|
213 |
into |
|
214 |
|
|
215 |
+ Movies |
|
216 |
- Silence of the Lambs |
|
217 |
- My Cousin Vinny |
|
218 |
+ Books |
|
219 |
- The Hunt for Red October |
|
220 |
- The Road to Omaha" |
|
221 |
:group 'org-plain-lists |
|
222 |
:type '(repeat |
|
223 |
(cons |
|
224 |
(choice :tag "If the current bullet is " |
|
225 |
(const "-") |
|
226 |
(const "+") |
|
227 |
(const "*") |
|
228 |
(const "1.") |
|
229 |
(const "1)")) |
|
230 |
(choice :tag "demotion will change it to" |
|
231 |
(const "-") |
|
232 |
(const "+") |
|
233 |
(const "*") |
|
234 |
(const "1.") |
|
235 |
(const "1)"))))) |
|
236 |
|
|
237 |
(defcustom org-plain-list-ordered-item-terminator t |
|
238 |
"The character that makes a line with leading number an ordered list item. |
|
239 |
Valid values are ?. and ?\). To get both terminators, use t. |
|
240 |
|
|
241 |
This variable needs to be set before org.el is loaded. If you |
|
242 |
need to make a change while Emacs is running, use the customize |
|
243 |
interface or run the following code after updating it: |
|
244 |
|
|
245 |
`\\[org-element-update-syntax]'" |
|
246 |
:group 'org-plain-lists |
|
247 |
:type '(choice (const :tag "dot like in \"2.\"" ?.) |
|
248 |
(const :tag "paren like in \"2)\"" ?\)) |
|
249 |
(const :tag "both" t)) |
|
250 |
:set (lambda (var val) (set var val) |
|
251 |
(when (featurep 'org-element) (org-element-update-syntax)))) |
|
252 |
|
|
253 |
(defcustom org-list-allow-alphabetical nil |
|
254 |
"Non-nil means single character alphabetical bullets are allowed. |
|
255 |
|
|
256 |
Both uppercase and lowercase are handled. Lists with more than |
|
257 |
26 items will fallback to standard numbering. Alphabetical |
|
258 |
counters like \"[@c]\" will be recognized. |
|
259 |
|
|
260 |
This variable needs to be set before org.el is loaded. If you |
|
261 |
need to make a change while Emacs is running, use the customize |
|
262 |
interface or run the following code after updating it: |
|
263 |
|
|
264 |
`\\[org-element-update-syntax]'" |
|
265 |
:group 'org-plain-lists |
|
266 |
:version "24.1" |
|
267 |
:type 'boolean |
|
268 |
:set (lambda (var val) (set var val) |
|
269 |
(when (featurep 'org-element) (org-element-update-syntax)))) |
|
270 |
|
|
271 |
(defcustom org-list-two-spaces-after-bullet-regexp nil |
|
272 |
"A regular expression matching bullets that should have 2 spaces after them. |
|
273 |
When nil, no bullet will have two spaces after them. When |
|
274 |
a string, it will be used as a regular expression. When the |
|
275 |
bullet type of a list is changed, the new bullet type will be |
|
276 |
matched against this regexp. If it matches, there will be two |
|
277 |
spaces instead of one after the bullet in each item of the list." |
|
278 |
:group 'org-plain-lists |
|
279 |
:type '(choice |
|
280 |
(const :tag "never" nil) |
|
281 |
(regexp))) |
|
282 |
|
|
283 |
(defcustom org-list-automatic-rules '((checkbox . t) |
|
284 |
(indent . t)) |
|
285 |
"Non-nil means apply set of rules when acting on lists. |
|
286 |
\\<org-mode-map> |
|
287 |
By default, automatic actions are taken when using |
|
288 |
`\\[org-meta-return]', |
|
289 |
`\\[org-metaright]', |
|
290 |
`\\[org-metaleft]', |
|
291 |
`\\[org-shiftmetaright]', |
|
292 |
`\\[org-shiftmetaleft]', |
|
293 |
`\\[org-ctrl-c-minus]', |
|
294 |
`\\[org-toggle-checkbox]', |
|
295 |
`\\[org-insert-todo-heading]'. |
|
296 |
|
|
297 |
You can disable individually these rules by setting them to nil. |
|
298 |
Valid rules are: |
|
299 |
|
|
300 |
checkbox when non-nil, checkbox statistics is updated each time |
|
301 |
you either insert a new checkbox or toggle a checkbox. |
|
302 |
indent when non-nil, indenting or outdenting list top-item |
|
303 |
with its subtree will move the whole list and |
|
304 |
outdenting a list whose bullet is * to column 0 will |
|
305 |
change that bullet to \"-\"." |
|
306 |
:group 'org-plain-lists |
|
307 |
:version "24.1" |
|
308 |
:type '(alist :tag "Sets of rules" |
|
309 |
:key-type |
|
310 |
(choice |
|
311 |
(const :tag "Checkbox" checkbox) |
|
312 |
(const :tag "Indent" indent)) |
|
313 |
:value-type |
|
314 |
(boolean :tag "Activate" :value t))) |
|
315 |
|
|
316 |
(defcustom org-list-use-circular-motion nil |
|
317 |
"Non-nil means commands implying motion in lists should be cyclic. |
|
318 |
\\<org-mode-map> |
|
319 |
In that case, the item following the last item is the first one, |
|
320 |
and the item preceding the first item is the last one. |
|
321 |
|
|
322 |
This affects the behavior of |
|
323 |
`\\[org-move-item-up]', |
|
324 |
`\\[org-move-item-down]', |
|
325 |
`\\[org-next-item]', |
|
326 |
`\\[org-previous-item]'." |
|
327 |
:group 'org-plain-lists |
|
328 |
:version "24.1" |
|
329 |
:type 'boolean) |
|
330 |
|
|
331 |
(defvar org-checkbox-statistics-hook nil |
|
332 |
"Hook that is run whenever Org thinks checkbox statistics should be updated. |
|
333 |
This hook runs even if checkbox rule in |
|
334 |
`org-list-automatic-rules' does not apply, so it can be used to |
|
335 |
implement alternative ways of collecting statistics |
|
336 |
information.") |
|
337 |
|
|
338 |
(defcustom org-checkbox-hierarchical-statistics t |
|
339 |
"Non-nil means checkbox statistics counts only the state of direct children. |
|
340 |
When nil, all boxes below the cookie are counted. |
|
341 |
This can be set to nil on a per-node basis using a COOKIE_DATA property |
|
342 |
with the word \"recursive\" in the value." |
|
343 |
:group 'org-plain-lists |
|
344 |
:type 'boolean) |
|
345 |
|
|
346 |
(defcustom org-list-description-max-indent 20 |
|
347 |
"Maximum indentation for the second line of a description list. |
|
348 |
When the indentation would be larger than this, it will become |
|
349 |
5 characters instead." |
|
350 |
:group 'org-plain-lists |
|
351 |
:type 'integer) |
|
352 |
|
|
353 |
(defcustom org-list-indent-offset 0 |
|
354 |
"Additional indentation for sub-items in a list. |
|
355 |
By setting this to a small number, usually 1 or 2, one can more |
|
356 |
clearly distinguish sub-items in a list." |
|
357 |
:group 'org-plain-lists |
|
358 |
:version "24.1" |
|
359 |
:type 'integer) |
|
360 |
|
|
361 |
(defcustom org-list-radio-list-templates |
|
362 |
'((latex-mode "% BEGIN RECEIVE ORGLST %n |
|
363 |
% END RECEIVE ORGLST %n |
|
364 |
\\begin{comment} |
|
365 |
#+ORGLST: SEND %n org-list-to-latex |
|
366 |
- |
|
367 |
\\end{comment}\n") |
|
368 |
(texinfo-mode "@c BEGIN RECEIVE ORGLST %n |
|
369 |
@c END RECEIVE ORGLST %n |
|
370 |
@ignore |
|
371 |
#+ORGLST: SEND %n org-list-to-texinfo |
|
372 |
- |
|
373 |
@end ignore\n") |
|
374 |
(html-mode "<!-- BEGIN RECEIVE ORGLST %n --> |
|
375 |
<!-- END RECEIVE ORGLST %n --> |
|
376 |
<!-- |
|
377 |
#+ORGLST: SEND %n org-list-to-html |
|
378 |
- |
|
379 |
-->\n")) |
|
380 |
"Templates for radio lists in different major modes. |
|
381 |
All occurrences of %n in a template will be replaced with the name of the |
|
382 |
list, obtained by prompting the user." |
|
383 |
:group 'org-plain-lists |
|
384 |
:type '(repeat |
|
385 |
(list (symbol :tag "Major mode") |
|
386 |
(string :tag "Format")))) |
|
387 |
|
|
388 |
(defvar org-list-forbidden-blocks '("example" "verse" "src" "export") |
|
389 |
"Names of blocks where lists are not allowed. |
|
390 |
Names must be in lower case.") |
|
391 |
|
|
392 |
(defvar org-list-export-context '(block inlinetask) |
|
393 |
"Context types where lists will be interpreted during export. |
|
394 |
|
|
395 |
Valid types are `drawer', `inlinetask' and `block'. More |
|
396 |
specifically, type `block' is determined by the variable |
|
397 |
`org-list-forbidden-blocks'.") |
|
398 |
|
|
399 |
|
|
400 |
|
|
401 |
;;; Predicates and regexps |
|
402 |
|
|
403 |
(defconst org-list-end-re "^[ \t]*\n[ \t]*\n" |
|
404 |
"Regex matching the end of a plain list.") |
|
405 |
|
|
406 |
(defconst org-list-full-item-re |
|
407 |
(concat "^[ \t]*\\(\\(?:[-+*]\\|\\(?:[0-9]+\\|[A-Za-z]\\)[.)]\\)\\(?:[ \t]+\\|$\\)\\)" |
|
408 |
"\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?" |
|
409 |
"\\(?:\\(\\[[ X-]\\]\\)\\(?:[ \t]+\\|$\\)\\)?" |
|
410 |
"\\(?:\\(.*\\)[ \t]+::\\(?:[ \t]+\\|$\\)\\)?") |
|
411 |
"Matches a list item and puts everything into groups: |
|
412 |
group 1: bullet |
|
413 |
group 2: counter |
|
414 |
group 3: checkbox |
|
415 |
group 4: description tag") |
|
416 |
|
|
417 |
(defun org-item-re () |
|
418 |
"Return the correct regular expression for plain lists." |
|
419 |
(let ((term (cond |
|
420 |
((eq org-plain-list-ordered-item-terminator t) "[.)]") |
|
421 |
((= org-plain-list-ordered-item-terminator ?\)) ")") |
|
422 |
((= org-plain-list-ordered-item-terminator ?.) "\\.") |
|
423 |
(t "[.)]"))) |
|
424 |
(alpha (if org-list-allow-alphabetical "\\|[A-Za-z]" ""))) |
|
425 |
(concat "\\([ \t]*\\([-+]\\|\\(\\([0-9]+" alpha "\\)" term |
|
426 |
"\\)\\)\\|[ \t]+\\*\\)\\([ \t]+\\|$\\)"))) |
|
427 |
|
|
428 |
(defsubst org-item-beginning-re () |
|
429 |
"Regexp matching the beginning of a plain list item." |
|
430 |
(concat "^" (org-item-re))) |
|
431 |
|
|
432 |
(defun org-list-at-regexp-after-bullet-p (regexp) |
|
433 |
"Is point at a list item with REGEXP after bullet?" |
|
434 |
(and (org-at-item-p) |
|
435 |
(save-excursion |
|
436 |
(goto-char (match-end 0)) |
|
437 |
(let ((counter-re (concat "\\(?:\\[@\\(?:start:\\)?" |
|
438 |
(if org-list-allow-alphabetical |
|
439 |
"\\([0-9]+\\|[A-Za-z]\\)" |
|
440 |
"[0-9]+") |
|
441 |
"\\][ \t]*\\)"))) |
|
442 |
;; Ignore counter if any |
|
443 |
(when (looking-at counter-re) (goto-char (match-end 0)))) |
|
444 |
(looking-at regexp)))) |
|
445 |
|
|
446 |
(defun org-list-in-valid-context-p () |
|
447 |
"Is point in a context where lists are allowed?" |
|
448 |
(not (org-in-block-p org-list-forbidden-blocks))) |
|
449 |
|
|
450 |
(defun org-in-item-p () |
|
451 |
"Return item beginning position when in a plain list, nil otherwise." |
|
452 |
(save-excursion |
|
453 |
(beginning-of-line) |
|
454 |
(let* ((case-fold-search t) |
|
455 |
(context (org-list-context)) |
|
456 |
(lim-up (car context)) |
|
457 |
(inlinetask-re (and (featurep 'org-inlinetask) |
|
458 |
(org-inlinetask-outline-regexp))) |
|
459 |
(item-re (org-item-re)) |
|
460 |
;; Indentation isn't meaningful when point starts at an empty |
|
461 |
;; line or an inline task. |
|
462 |
(ind-ref (if (or (looking-at "^[ \t]*$") |
|
463 |
(and inlinetask-re (looking-at inlinetask-re))) |
|
464 |
10000 |
|
465 |
(org-get-indentation)))) |
|
466 |
(cond |
|
467 |
((eq (nth 2 context) 'invalid) nil) |
|
468 |
((looking-at item-re) (point)) |
|
469 |
(t |
|
470 |
;; Detect if cursor in amidst `org-list-end-re'. First, count |
|
471 |
;; number HL of hard lines it takes, then call `org-in-regexp' |
|
472 |
;; to compute its boundaries END-BOUNDS. When point is |
|
473 |
;; in-between, move cursor before regexp beginning. |
|
474 |
(let ((hl 0) (i -1) end-bounds) |
|
475 |
(when (and (progn |
|
476 |
(while (setq i (string-match |
|
477 |
"[\r\n]" org-list-end-re (1+ i))) |
|
478 |
(setq hl (1+ hl))) |
|
479 |
(setq end-bounds (org-in-regexp org-list-end-re hl))) |
|
480 |
(>= (point) (car end-bounds)) |
|
481 |
(< (point) (cdr end-bounds))) |
|
482 |
(goto-char (car end-bounds)) |
|
483 |
(forward-line -1))) |
|
484 |
;; Look for an item, less indented that reference line. |
|
485 |
(catch 'exit |
|
486 |
(while t |
|
487 |
(let ((ind (org-get-indentation))) |
|
488 |
(cond |
|
489 |
;; This is exactly what we want. |
|
490 |
((and (looking-at item-re) (< ind ind-ref)) |
|
491 |
(throw 'exit (point))) |
|
492 |
;; At upper bound of search or looking at the end of a |
|
493 |
;; previous list: search is over. |
|
494 |
((<= (point) lim-up) (throw 'exit nil)) |
|
495 |
((looking-at org-list-end-re) (throw 'exit nil)) |
|
496 |
;; Skip blocks, drawers, inline-tasks, blank lines |
|
497 |
((and (looking-at "^[ \t]*#\\+end_") |
|
498 |
(re-search-backward "^[ \t]*#\\+begin_" lim-up t))) |
|
499 |
((and (looking-at "^[ \t]*:END:") |
|
500 |
(re-search-backward org-drawer-regexp lim-up t)) |
|
501 |
(beginning-of-line)) |
|
502 |
((and inlinetask-re (looking-at inlinetask-re)) |
|
503 |
(org-inlinetask-goto-beginning) |
|
504 |
(forward-line -1)) |
|
505 |
((looking-at "^[ \t]*$") (forward-line -1)) |
|
506 |
;; Text at column 0 cannot belong to a list: stop. |
|
507 |
((zerop ind) (throw 'exit nil)) |
|
508 |
;; Normal text less indented than reference line, take |
|
509 |
;; it as new reference. |
|
510 |
((< ind ind-ref) |
|
511 |
(setq ind-ref ind) |
|
512 |
(forward-line -1)) |
|
513 |
(t (forward-line -1))))))))))) |
|
514 |
|
|
515 |
(defun org-at-item-p () |
|
516 |
"Is point in a line starting a hand-formatted item?" |
|
517 |
(save-excursion |
|
518 |
(beginning-of-line) |
|
519 |
(and (looking-at (org-item-re)) (org-list-in-valid-context-p)))) |
|
520 |
|
|
521 |
(defun org-at-item-bullet-p () |
|
522 |
"Is point at the bullet of a plain list item?" |
|
523 |
(and (org-at-item-p) |
|
524 |
(not (member (char-after) '(?\ ?\t))) |
|
525 |
(< (point) (match-end 0)))) |
|
526 |
|
|
527 |
(defun org-at-item-timer-p () |
|
528 |
"Is point at a line starting a plain list item with a timer?" |
|
529 |
(org-list-at-regexp-after-bullet-p |
|
530 |
"\\([0-9]+:[0-9]+:[0-9]+\\)[ \t]+::[ \t]+")) |
|
531 |
|
|
532 |
(defun org-at-item-description-p () |
|
533 |
"Is point at a description list item?" |
|
534 |
(org-list-at-regexp-after-bullet-p "\\(\\S-.+\\)[ \t]+::\\([ \t]+\\|$\\)")) |
|
535 |
|
|
536 |
(defun org-at-item-checkbox-p () |
|
537 |
"Is point at a line starting a plain-list item with a checklet?" |
|
538 |
(org-list-at-regexp-after-bullet-p "\\(\\[[- X]\\]\\)[ \t]+")) |
|
539 |
|
|
540 |
(defun org-at-item-counter-p () |
|
541 |
"Is point at a line starting a plain-list item with a counter?" |
|
542 |
(and (org-at-item-p) |
|
543 |
(looking-at org-list-full-item-re) |
|
544 |
(match-string 2))) |
|
545 |
|
|
546 |
|
|
547 |
|
|
548 |
;;; Structures and helper functions |
|
549 |
|
|
550 |
(defun org-list-context () |
|
551 |
"Determine context, and its boundaries, around point. |
|
552 |
|
|
553 |
Context will be a cell like (MIN MAX CONTEXT) where MIN and MAX |
|
554 |
are boundaries and CONTEXT is a symbol among `drawer', `block', |
|
555 |
`invalid', `inlinetask' and nil. |
|
556 |
|
|
557 |
Contexts `block' and `invalid' refer to `org-list-forbidden-blocks'." |
|
558 |
(save-match-data |
|
559 |
(save-excursion |
|
560 |
(org-with-limited-levels |
|
561 |
(beginning-of-line) |
|
562 |
(let ((case-fold-search t) (pos (point)) beg end context-type |
|
563 |
;; Get positions of surrounding headings. This is the |
|
564 |
;; default context. |
|
565 |
(lim-up (or (save-excursion (and (ignore-errors (org-back-to-heading t)) |
|
566 |
(point))) |
|
567 |
(point-min))) |
|
568 |
(lim-down (or (save-excursion (outline-next-heading)) (point-max)))) |
|
569 |
;; Is point inside a drawer? |
|
570 |
(let ((end-re "^[ \t]*:END:") |
|
571 |
(beg-re org-drawer-regexp)) |
|
572 |
(when (save-excursion |
|
573 |
(and (not (looking-at beg-re)) |
|
574 |
(not (looking-at end-re)) |
|
575 |
(setq beg (and (re-search-backward beg-re lim-up t) |
|
576 |
(1+ (point-at-eol)))) |
|
577 |
(setq end (or (and (re-search-forward end-re lim-down t) |
|
578 |
(1- (match-beginning 0))) |
|
579 |
lim-down)) |
|
580 |
(>= end pos))) |
|
581 |
(setq lim-up beg lim-down end context-type 'drawer))) |
|
582 |
;; Is point strictly in a block, and of which type? |
|
583 |
(let ((block-re "^[ \t]*#\\+\\(begin\\|end\\)_") type) |
|
584 |
(when (save-excursion |
|
585 |
(and (not (looking-at block-re)) |
|
586 |
(setq beg (and (re-search-backward block-re lim-up t) |
|
587 |
(1+ (point-at-eol)))) |
|
588 |
(looking-at "^[ \t]*#\\+begin_\\(\\S-+\\)") |
|
589 |
(setq type (downcase (match-string 1))) |
|
590 |
(goto-char beg) |
|
591 |
(setq end (or (and (re-search-forward block-re lim-down t) |
|
592 |
(1- (point-at-bol))) |
|
593 |
lim-down)) |
|
594 |
(>= end pos) |
|
595 |
(equal (downcase (match-string 1)) "end"))) |
|
596 |
(setq lim-up beg lim-down end |
|
597 |
context-type (if (member type org-list-forbidden-blocks) |
|
598 |
'invalid 'block)))) |
|
599 |
;; Is point in an inlinetask? |
|
600 |
(when (and (featurep 'org-inlinetask) |
|
601 |
(save-excursion |
|
602 |
(let* ((beg-re (org-inlinetask-outline-regexp)) |
|
603 |
(end-re (concat beg-re "END[ \t]*$"))) |
|
604 |
(and (not (looking-at "^\\*+")) |
|
605 |
(setq beg (and (re-search-backward beg-re lim-up t) |
|
606 |
(1+ (point-at-eol)))) |
|
607 |
(not (looking-at end-re)) |
|
608 |
(setq end (and (re-search-forward end-re lim-down t) |
|
609 |
(1- (match-beginning 0)))) |
|
610 |
(> (point) pos))))) |
|
611 |
(setq lim-up beg lim-down end context-type 'inlinetask)) |
|
612 |
;; Return context boundaries and type. |
|
613 |
(list lim-up lim-down context-type)))))) |
|
614 |
|
|
615 |
(defun org-list-struct () |
|
616 |
"Return structure of list at point. |
|
617 |
|
|
618 |
A list structure is an alist where key is point at item, and |
|
619 |
values are: |
|
620 |
1. indentation, |
|
621 |
2. bullet with trailing whitespace, |
|
622 |
3. bullet counter, if any, |
|
623 |
4. checkbox, if any, |
|
624 |
5. description tag, if any, |
|
625 |
6. position at item end. |
|
626 |
|
|
627 |
Thus the following list, where numbers in parens are |
|
628 |
point-at-bol: |
|
629 |
|
|
630 |
- [X] first item (1) |
|
631 |
1. sub-item 1 (18) |
|
632 |
5. [@5] sub-item 2 (34) |
|
633 |
some other text belonging to first item (55) |
|
634 |
- last item (97) |
|
635 |
+ tag :: description (109) |
|
636 |
(131) |
|
637 |
|
|
638 |
will get the following structure: |
|
639 |
|
|
640 |
((1 0 \"- \" nil \"[X]\" nil 97) |
|
641 |
(18 2 \"1. \" nil nil nil 34) |
|
642 |
(34 2 \"5. \" \"5\" nil nil 55) |
|
643 |
(97 0 \"- \" nil nil nil 131) |
|
644 |
(109 2 \"+ \" nil nil \"tag\" 131)) |
|
645 |
|
|
646 |
Assume point is at an item." |
|
647 |
(save-excursion |
|
648 |
(beginning-of-line) |
|
649 |
(let* ((case-fold-search t) |
|
650 |
(context (org-list-context)) |
|
651 |
(lim-up (car context)) |
|
652 |
(lim-down (nth 1 context)) |
|
653 |
(text-min-ind 10000) |
|
654 |
(item-re (org-item-re)) |
|
655 |
(inlinetask-re (and (featurep 'org-inlinetask) |
|
656 |
(org-inlinetask-outline-regexp))) |
|
657 |
(beg-cell (cons (point) (org-get-indentation))) |
|
658 |
itm-lst itm-lst-2 end-lst end-lst-2 struct |
|
659 |
(assoc-at-point |
|
660 |
(function |
|
661 |
;; Return association at point. |
|
662 |
(lambda (ind) |
|
663 |
(looking-at org-list-full-item-re) |
|
664 |
(let ((bullet (match-string-no-properties 1))) |
|
665 |
(list (point) |
|
666 |
ind |
|
667 |
bullet |
|
668 |
(match-string-no-properties 2) ; counter |
|
669 |
(match-string-no-properties 3) ; checkbox |
|
670 |
;; Description tag. |
|
671 |
(and (string-match-p "[-+*]" bullet) |
|
672 |
(match-string-no-properties 4))))))) |
|
673 |
(end-before-blank |
|
674 |
(function |
|
675 |
;; Ensure list ends at the first blank line. |
|
676 |
(lambda () |
|
677 |
(skip-chars-backward " \r\t\n") |
|
678 |
(min (1+ (point-at-eol)) lim-down))))) |
|
679 |
;; 1. Read list from starting item to its beginning, and save |
|
680 |
;; top item position and indentation in BEG-CELL. Also store |
|
681 |
;; ending position of items in END-LST. |
|
682 |
(save-excursion |
|
683 |
(catch 'exit |
|
684 |
(while t |
|
685 |
(let ((ind (org-get-indentation))) |
|
686 |
(cond |
|
687 |
((<= (point) lim-up) |
|
688 |
;; At upward limit: if we ended at an item, store it, |
|
689 |
;; else dismiss useless data recorded above BEG-CELL. |
|
690 |
;; Jump to part 2. |
|
691 |
(throw 'exit |
|
692 |
(setq itm-lst |
|
693 |
(if (not (looking-at item-re)) |
|
694 |
(memq (assq (car beg-cell) itm-lst) itm-lst) |
|
695 |
(setq beg-cell (cons (point) ind)) |
|
696 |
(cons (funcall assoc-at-point ind) itm-lst))))) |
|
697 |
;; Looking at a list ending regexp. Dismiss useless |
|
698 |
;; data recorded above BEG-CELL. Jump to part 2. |
|
699 |
((looking-at org-list-end-re) |
|
700 |
(throw 'exit |
|
701 |
(setq itm-lst |
|
702 |
(memq (assq (car beg-cell) itm-lst) itm-lst)))) |
|
703 |
;; Point is at an item. Add data to ITM-LST. It may |
|
704 |
;; also end a previous item: save it in END-LST. If |
|
705 |
;; ind is less or equal than BEG-CELL and there is no |
|
706 |
;; end at this ind or lesser, this item becomes the new |
|
707 |
;; BEG-CELL. |
|
708 |
((looking-at item-re) |
|
709 |
(push (funcall assoc-at-point ind) itm-lst) |
|
710 |
(push (cons ind (point)) end-lst) |
|
711 |
(when (< ind text-min-ind) (setq beg-cell (cons (point) ind))) |
|
712 |
(forward-line -1)) |
|
713 |
;; Skip blocks, drawers, inline tasks, blank lines. |
|
714 |
((and (looking-at "^[ \t]*#\\+end_") |
|
715 |
(re-search-backward "^[ \t]*#\\+begin_" lim-up t))) |
|
716 |
((and (looking-at "^[ \t]*:END:") |
|
717 |
(re-search-backward org-drawer-regexp lim-up t)) |
|
718 |
(beginning-of-line)) |
|
719 |
((and inlinetask-re (looking-at inlinetask-re)) |
|
720 |
(org-inlinetask-goto-beginning) |
|
721 |
(forward-line -1)) |
|
722 |
((looking-at "^[ \t]*$") |
|
723 |
(forward-line -1)) |
|
724 |
;; From there, point is not at an item. Interpret |
|
725 |
;; line's indentation: |
|
726 |
;; - text at column 0 is necessarily out of any list. |
|
727 |
;; Dismiss data recorded above BEG-CELL. Jump to |
|
728 |
;; part 2. |
|
729 |
;; - any other case may be an ending position for an |
|
730 |
;; hypothetical item above. Store it and proceed. |
|
731 |
((zerop ind) |
|
732 |
(throw 'exit |
|
733 |
(setq itm-lst |
|
734 |
(memq (assq (car beg-cell) itm-lst) itm-lst)))) |
|
735 |
(t |
|
736 |
(when (< ind text-min-ind) (setq text-min-ind ind)) |
|
737 |
(push (cons ind (point)) end-lst) |
|
738 |
(forward-line -1))))))) |
|
739 |
;; 2. Read list from starting point to its end, that is until we |
|
740 |
;; get out of context, or that a non-item line is less or |
|
741 |
;; equally indented than BEG-CELL's cdr. Also, store ending |
|
742 |
;; position of items in END-LST-2. |
|
743 |
(catch 'exit |
|
744 |
(while t |
|
745 |
(let ((ind (org-get-indentation))) |
|
746 |
(cond |
|
747 |
((>= (point) lim-down) |
|
748 |
;; At downward limit: this is de facto the end of the |
|
749 |
;; list. Save point as an ending position, and jump to |
|
750 |
;; part 3. |
|
751 |
(throw 'exit |
|
752 |
(push (cons 0 (funcall end-before-blank)) end-lst-2))) |
|
753 |
;; Looking at a list ending regexp. Save point as an |
|
754 |
;; ending position and jump to part 3. |
|
755 |
((looking-at org-list-end-re) |
|
756 |
(throw 'exit (push (cons 0 (point)) end-lst-2))) |
|
757 |
((looking-at item-re) |
|
758 |
;; Point is at an item. Add data to ITM-LST-2. It may |
|
759 |
;; also end a previous item, so save it in END-LST-2. |
|
760 |
(push (funcall assoc-at-point ind) itm-lst-2) |
|
761 |
(push (cons ind (point)) end-lst-2) |
|
762 |
(forward-line 1)) |
|
763 |
;; Skip inline tasks and blank lines along the way |
|
764 |
((and inlinetask-re (looking-at inlinetask-re)) |
|
765 |
(org-inlinetask-goto-end)) |
|
766 |
((looking-at "^[ \t]*$") |
|
767 |
(forward-line 1)) |
|
768 |
;; Ind is lesser or equal than BEG-CELL's. The list is |
|
769 |
;; over: store point as an ending position and jump to |
|
770 |
;; part 3. |
|
771 |
((<= ind (cdr beg-cell)) |
|
772 |
(throw 'exit |
|
773 |
(push (cons 0 (funcall end-before-blank)) end-lst-2))) |
|
774 |
;; Else, if ind is lesser or equal than previous item's, |
|
775 |
;; this is an ending position: store it. In any case, |
|
776 |
;; skip block or drawer at point, and move to next line. |
|
777 |
(t |
|
778 |
(when (<= ind (nth 1 (car itm-lst-2))) |
|
779 |
(push (cons ind (point)) end-lst-2)) |
|
780 |
(cond |
|
781 |
((and (looking-at "^[ \t]*#\\+begin_") |
|
782 |
(re-search-forward "^[ \t]*#\\+end_" lim-down t))) |
|
783 |
((and (looking-at org-drawer-regexp) |
|
784 |
(re-search-forward "^[ \t]*:END:" lim-down t)))) |
|
785 |
(forward-line 1)))))) |
|
786 |
(setq struct (append itm-lst (cdr (nreverse itm-lst-2))) |
|
787 |
end-lst (append end-lst (cdr (nreverse end-lst-2)))) |
|
788 |
;; 3. Associate each item to its end position. |
|
789 |
(org-list-struct-assoc-end struct end-lst) |
|
790 |
;; 4. Return STRUCT |
|
791 |
struct))) |
|
792 |
|
|
793 |
(defun org-list-struct-assoc-end (struct end-list) |
|
794 |
"Associate proper ending point to items in STRUCT. |
|
795 |
|
|
796 |
END-LIST is a pseudo-alist where car is indentation and cdr is |
|
797 |
ending position. |
|
798 |
|
|
799 |
This function modifies STRUCT." |
|
800 |
(let ((endings end-list)) |
|
801 |
(mapc |
|
802 |
(lambda (elt) |
|
803 |
(let ((pos (car elt)) |
|
804 |
(ind (nth 1 elt))) |
|
805 |
;; Remove end candidates behind current item. |
|
806 |
(while (or (<= (cdar endings) pos)) |
|
807 |
(pop endings)) |
|
808 |
;; Add end position to item assoc. |
|
809 |
(let ((old-end (nthcdr 6 elt)) |
|
810 |
(new-end (assoc-default ind endings '<=))) |
|
811 |
(if old-end |
|
812 |
(setcar old-end new-end) |
|
813 |
(setcdr elt (append (cdr elt) (list new-end))))))) |
|
814 |
struct))) |
|
815 |
|
|
816 |
(defun org-list-prevs-alist (struct) |
|
817 |
"Return alist between item and previous item in STRUCT." |
|
818 |
(let ((item-end-alist (mapcar (lambda (e) (cons (car e) (nth 6 e))) |
|
819 |
struct))) |
|
820 |
(mapcar (lambda (e) |
|
821 |
(let ((prev (car (rassq (car e) item-end-alist)))) |
|
822 |
(cons (car e) prev))) |
|
823 |
struct))) |
|
824 |
|
|
825 |
(defun org-list-parents-alist (struct) |
|
826 |
"Return alist between item and parent in STRUCT." |
|
827 |
(let* ((ind-to-ori (list (list (nth 1 (car struct))))) |
|
828 |
(top-item (org-list-get-top-point struct)) |
|
829 |
(prev-pos (list top-item))) |
|
830 |
(cons prev-pos |
|
831 |
(mapcar (lambda (item) |
|
832 |
(let ((pos (car item)) |
|
833 |
(ind (nth 1 item)) |
|
834 |
(prev-ind (caar ind-to-ori))) |
|
835 |
(push pos prev-pos) |
|
836 |
(cond |
|
837 |
((> prev-ind ind) |
|
838 |
;; A sub-list is over. Find the associated |
|
839 |
;; origin in IND-TO-ORI. If it cannot be |
|
840 |
;; found (ill-formed list), set its parent as |
|
841 |
;; the first item less indented. If there is |
|
842 |
;; none, make it a top-level item. |
|
843 |
(setq ind-to-ori |
|
844 |
(or (member (assq ind ind-to-ori) ind-to-ori) |
|
845 |
(catch 'exit |
|
846 |
(mapc |
|
847 |
(lambda (e) |
|
848 |
(when (< (car e) ind) |
|
849 |
(throw 'exit (member e ind-to-ori)))) |
|
850 |
ind-to-ori) |
|
851 |
(list (list ind))))) |
|
852 |
(cons pos (cdar ind-to-ori))) |
|
853 |
;; A sub-list starts. Every item at IND will |
|
854 |
;; have previous item as its parent. |
|
855 |
((< prev-ind ind) |
|
856 |
(let ((origin (nth 1 prev-pos))) |
|
857 |
(push (cons ind origin) ind-to-ori) |
|
858 |
(cons pos origin))) |
|
859 |
;; Another item in the same sub-list: it shares |
|
860 |
;; the same parent as the previous item. |
|
861 |
(t (cons pos (cdar ind-to-ori)))))) |
|
862 |
(cdr struct))))) |
|
863 |
|
|
864 |
|
|
865 |
|
|
866 |
;;; Accessors |
|
867 |
|
|
868 |
(defsubst org-list-get-nth (n key struct) |
|
869 |
"Return the Nth value of KEY in STRUCT." |
|
870 |
(nth n (assq key struct))) |
|
871 |
|
|
872 |
(defun org-list-set-nth (n key struct new) |
|
873 |
"Set the Nth value of KEY in STRUCT to NEW. |
|
874 |
\nThis function modifies STRUCT." |
|
875 |
(setcar (nthcdr n (assq key struct)) new)) |
|
876 |
|
|
877 |
(defsubst org-list-get-ind (item struct) |
|
878 |
"Return indentation of ITEM in STRUCT." |
|
879 |
(org-list-get-nth 1 item struct)) |
|
880 |
|
|
881 |
(defun org-list-set-ind (item struct ind) |
|
882 |
"Set indentation of ITEM in STRUCT to IND. |
|
883 |
\nThis function modifies STRUCT." |
|
884 |
(org-list-set-nth 1 item struct ind)) |
|
885 |
|
|
886 |
(defsubst org-list-get-bullet (item struct) |
|
887 |
"Return bullet of ITEM in STRUCT." |
|
888 |
(org-list-get-nth 2 item struct)) |
|
889 |
|
|
890 |
(defun org-list-set-bullet (item struct bullet) |
|
891 |
"Set bullet of ITEM in STRUCT to BULLET. |
|
892 |
\nThis function modifies STRUCT." |
|
893 |
(org-list-set-nth 2 item struct bullet)) |
|
894 |
|
|
895 |
(defsubst org-list-get-counter (item struct) |
|
896 |
"Return counter of ITEM in STRUCT." |
|
897 |
(org-list-get-nth 3 item struct)) |
|
898 |
|
|
899 |
(defsubst org-list-get-checkbox (item struct) |
|
900 |
"Return checkbox of ITEM in STRUCT or nil." |
|
901 |
(org-list-get-nth 4 item struct)) |
|
902 |
|
|
903 |
(defun org-list-set-checkbox (item struct checkbox) |
|
904 |
"Set checkbox of ITEM in STRUCT to CHECKBOX. |
|
905 |
\nThis function modifies STRUCT." |
|
906 |
(org-list-set-nth 4 item struct checkbox)) |
|
907 |
|
|
908 |
(defsubst org-list-get-tag (item struct) |
|
909 |
"Return end position of ITEM in STRUCT." |
|
910 |
(org-list-get-nth 5 item struct)) |
|
911 |
|
|
912 |
(defun org-list-get-item-end (item struct) |
|
913 |
"Return end position of ITEM in STRUCT." |
|
914 |
(org-list-get-nth 6 item struct)) |
|
915 |
|
|
916 |
(defun org-list-get-item-end-before-blank (item struct) |
|
917 |
"Return point at end of ITEM in STRUCT, before any blank line. |
|
918 |
Point returned is at end of line." |
|
919 |
(save-excursion |
|
920 |
(goto-char (org-list-get-item-end item struct)) |
|
921 |
(skip-chars-backward " \r\t\n") |
|
922 |
(point-at-eol))) |
|
923 |
|
|
924 |
(defun org-list-get-parent (item struct parents) |
|
925 |
"Return parent of ITEM or nil. |
|
926 |
STRUCT is the list structure. PARENTS is the alist of parents, |
|
927 |
as returned by `org-list-parents-alist'." |
|
928 |
(let ((parents (or parents (org-list-parents-alist struct)))) |
|
929 |
(cdr (assq item parents)))) |
|
930 |
|
|
931 |
(defun org-list-has-child-p (item struct) |
|
932 |
"Non-nil if ITEM has a child. |
|
933 |
|
|
934 |
STRUCT is the list structure. |
|
935 |
|
|
936 |
Value returned is the position of the first child of ITEM." |
|
937 |
(let ((ind (org-list-get-ind item struct)) |
|
938 |
(child-maybe (car (nth 1 (member (assq item struct) struct))))) |
|
939 |
(when (and child-maybe |
|
940 |
(< ind (org-list-get-ind child-maybe struct))) |
|
941 |
child-maybe))) |
|
942 |
|
|
943 |
(defun org-list-get-next-item (item _struct prevs) |
|
944 |
"Return next item in same sub-list as ITEM, or nil. |
|
945 |
STRUCT is the list structure. PREVS is the alist of previous |
|
946 |
items, as returned by `org-list-prevs-alist'." |
|
947 |
(car (rassq item prevs))) |
|
948 |
|
|
949 |
(defun org-list-get-prev-item (item _struct prevs) |
|
950 |
"Return previous item in same sub-list as ITEM, or nil. |
|
951 |
STRUCT is the list structure. PREVS is the alist of previous |
|
952 |
items, as returned by `org-list-prevs-alist'." |
|
953 |
(cdr (assq item prevs))) |
|
954 |
|
|
955 |
(defun org-list-get-subtree (item struct) |
|
956 |
"List all items having ITEM as a common ancestor, or nil. |
|
957 |
STRUCT is the list structure." |
|
958 |
(let* ((item-end (org-list-get-item-end item struct)) |
|
959 |
(sub-struct (cdr (member (assq item struct) struct))) |
|
960 |
subtree) |
|
961 |
(catch 'exit |
|
962 |
(mapc (lambda (e) |
|
963 |
(let ((pos (car e))) |
|
964 |
(if (< pos item-end) (push pos subtree) (throw 'exit nil)))) |
|
965 |
sub-struct)) |
|
966 |
(nreverse subtree))) |
|
967 |
|
|
968 |
(defun org-list-get-all-items (item struct prevs) |
|
969 |
"List all items in the same sub-list as ITEM. |
|
970 |
STRUCT is the list structure. PREVS is the alist of previous |
|
971 |
items, as returned by `org-list-prevs-alist'." |
|
972 |
(let ((prev-item item) |
|
973 |
(next-item item) |
|
974 |
before-item after-item) |
|
975 |
(while (setq prev-item (org-list-get-prev-item prev-item struct prevs)) |
|
976 |
(push prev-item before-item)) |
|
977 |
(while (setq next-item (org-list-get-next-item next-item struct prevs)) |
|
978 |
(push next-item after-item)) |
|
979 |
(append before-item (list item) (nreverse after-item)))) |
|
980 |
|
|
981 |
(defun org-list-get-children (item _struct parents) |
|
982 |
"List all children of ITEM, or nil. |
|
983 |
STRUCT is the list structure. PARENTS is the alist of parents, |
|
984 |
as returned by `org-list-parents-alist'." |
|
985 |
(let (all child) |
|
986 |
(while (setq child (car (rassq item parents))) |
|
987 |
(setq parents (cdr (member (assq child parents) parents))) |
|
988 |
(push child all)) |
|
989 |
(nreverse all))) |
|
990 |
|
|
991 |
(defun org-list-get-top-point (struct) |
|
992 |
"Return point at beginning of list. |
|
993 |
STRUCT is the list structure." |
|
994 |
(caar struct)) |
|
995 |
|
|
996 |
(defun org-list-get-bottom-point (struct) |
|
997 |
"Return point at bottom of list. |
|
998 |
STRUCT is the list structure." |
|
999 |
(apply #'max |
|
1000 |
(mapcar (lambda (e) (org-list-get-item-end (car e) struct)) struct))) |
|
1001 |
|
|
1002 |
(defun org-list-get-list-begin (item struct prevs) |
|
1003 |
"Return point at beginning of sub-list ITEM belongs. |
|
1004 |
STRUCT is the list structure. PREVS is the alist of previous |
|
1005 |
items, as returned by `org-list-prevs-alist'." |
|
1006 |
(let ((first-item item) prev-item) |
|
1007 |
(while (setq prev-item (org-list-get-prev-item first-item struct prevs)) |
|
1008 |
(setq first-item prev-item)) |
|
1009 |
first-item)) |
|
1010 |
|
|
1011 |
(defalias 'org-list-get-first-item 'org-list-get-list-begin) |
|
1012 |
|
|
1013 |
(defun org-list-get-last-item (item struct prevs) |
|
1014 |
"Return point at last item of sub-list ITEM belongs. |
|
1015 |
STRUCT is the list structure. PREVS is the alist of previous |
|
1016 |
items, as returned by `org-list-prevs-alist'." |
|
1017 |
(let ((last-item item) next-item) |
|
1018 |
(while (setq next-item (org-list-get-next-item last-item struct prevs)) |
|
1019 |
(setq last-item next-item)) |
|
1020 |
last-item)) |
|
1021 |
|
|
1022 |
(defun org-list-get-list-end (item struct prevs) |
|
1023 |
"Return point at end of sub-list ITEM belongs. |
|
1024 |
STRUCT is the list structure. PREVS is the alist of previous |
|
1025 |
items, as returned by `org-list-prevs-alist'." |
|
1026 |
(org-list-get-item-end (org-list-get-last-item item struct prevs) struct)) |
|
1027 |
|
|
1028 |
(defun org-list-get-list-type (item struct prevs) |
|
1029 |
"Return the type of the list containing ITEM, as a symbol. |
|
1030 |
|
|
1031 |
STRUCT is the list structure. PREVS is the alist of previous |
|
1032 |
items, as returned by `org-list-prevs-alist'. |
|
1033 |
|
|
1034 |
Possible types are `descriptive', `ordered' and `unordered'. The |
|
1035 |
type is determined by the first item of the list." |
|
1036 |
(let ((first (org-list-get-list-begin item struct prevs))) |
|
1037 |
(cond |
|
1038 |
((string-match-p "[[:alnum:]]" (org-list-get-bullet first struct)) 'ordered) |
|
1039 |
((org-list-get-tag first struct) 'descriptive) |
|
1040 |
(t 'unordered)))) |
|
1041 |
|
|
1042 |
(defun org-list-get-item-number (item struct prevs parents) |
|
1043 |
"Return ITEM's sequence number. |
|
1044 |
|
|
1045 |
STRUCT is the list structure. PREVS is the alist of previous |
|
1046 |
items, as returned by `org-list-prevs-alist'. PARENTS is the |
|
1047 |
alist of ancestors, as returned by `org-list-parents-alist'. |
|
1048 |
|
|
1049 |
Return value is a list of integers. Counters have an impact on |
|
1050 |
that value." |
|
1051 |
(let ((get-relative-number |
|
1052 |
(function |
|
1053 |
(lambda (item struct prevs) |
|
1054 |
;; Return relative sequence number of ITEM in the sub-list |
|
1055 |
;; it belongs. STRUCT is the list structure. PREVS is |
|
1056 |
;; the alist of previous items. |
|
1057 |
(let ((seq 0) (pos item) counter) |
|
1058 |
(while (and (not (setq counter (org-list-get-counter pos struct))) |
|
1059 |
(setq pos (org-list-get-prev-item pos struct prevs))) |
|
1060 |
(cl-incf seq)) |
|
1061 |
(if (not counter) (1+ seq) |
|
1062 |
(cond |
|
1063 |
((string-match "[A-Za-z]" counter) |
|
1064 |
(+ (- (string-to-char (upcase (match-string 0 counter))) 64) |
|
1065 |
seq)) |
|
1066 |
((string-match "[0-9]+" counter) |
|
1067 |
(+ (string-to-number (match-string 0 counter)) seq)) |
|
1068 |
(t (1+ seq))))))))) |
|
1069 |
;; Cons each parent relative number into return value (OUT). |
|
1070 |
(let ((out (list (funcall get-relative-number item struct prevs))) |
|
1071 |
(parent item)) |
|
1072 |
(while (setq parent (org-list-get-parent parent struct parents)) |
|
1073 |
(push (funcall get-relative-number parent struct prevs) out)) |
|
1074 |
;; Return value. |
|
1075 |
out))) |
|
1076 |
|
|
1077 |
|
|
1078 |
|
|
1079 |
;;; Searching |
|
1080 |
|
|
1081 |
(defun org-list-search-generic (search re bound noerr) |
|
1082 |
"Search a string in valid contexts for lists. |
|
1083 |
Arguments SEARCH, RE, BOUND and NOERR are similar to those used |
|
1084 |
in `re-search-forward'." |
|
1085 |
(catch 'exit |
|
1086 |
(let ((origin (point))) |
|
1087 |
(while t |
|
1088 |
;; 1. No match: return to origin or bound, depending on NOERR. |
|
1089 |
(unless (funcall search re bound noerr) |
|
1090 |
(throw 'exit (and (goto-char (if (memq noerr '(t nil)) origin bound)) |
|
1091 |
nil))) |
|
1092 |
;; 2. Match in valid context: return point. Else, continue |
|
1093 |
;; searching. |
|
1094 |
(when (org-list-in-valid-context-p) (throw 'exit (point))))))) |
|
1095 |
|
|
1096 |
(defun org-list-search-backward (regexp &optional bound noerror) |
|
1097 |
"Like `re-search-backward' but stop only where lists are recognized. |
|
1098 |
Arguments REGEXP, BOUND and NOERROR are similar to those used in |
|
1099 |
`re-search-backward'." |
|
1100 |
(org-list-search-generic #'re-search-backward |
|
1101 |
regexp (or bound (point-min)) noerror)) |
|
1102 |
|
|
1103 |
(defun org-list-search-forward (regexp &optional bound noerror) |
|
1104 |
"Like `re-search-forward' but stop only where lists are recognized. |
|
1105 |
Arguments REGEXP, BOUND and NOERROR are similar to those used in |
|
1106 |
`re-search-forward'." |
|
1107 |
(org-list-search-generic #'re-search-forward |
|
1108 |
regexp (or bound (point-max)) noerror)) |
|
1109 |
|
|
1110 |
|
|
1111 |
|
|
1112 |
;;; Methods on structures |
|
1113 |
|
|
1114 |
(defsubst org-list-bullet-string (bullet) |
|
1115 |
"Return BULLET with the correct number of whitespaces. |
|
1116 |
It determines the number of whitespaces to append by looking at |
|
1117 |
`org-list-two-spaces-after-bullet-regexp'." |
|
1118 |
(save-match-data |
|
1119 |
(let ((spaces (if (and org-list-two-spaces-after-bullet-regexp |
|
1120 |
(string-match |
|
1121 |
org-list-two-spaces-after-bullet-regexp bullet)) |
|
1122 |
" " |
|
1123 |
" "))) |
|
1124 |
(if (string-match "\\S-+\\([ \t]*\\)" bullet) |
|
1125 |
(replace-match spaces nil nil bullet 1) |
|
1126 |
bullet)))) |
|
1127 |
|
|
1128 |
(defun org-list-swap-items (beg-A beg-B struct) |
|
1129 |
"Swap item starting at BEG-A with item starting at BEG-B in STRUCT. |
|
1130 |
|
|
1131 |
Blank lines at the end of items are left in place. Item |
|
1132 |
visibility is preserved. Return the new structure after the |
|
1133 |
changes. |
|
1134 |
|
|
1135 |
Assume BEG-A is lesser than BEG-B and that BEG-A and BEG-B belong |
|
1136 |
to the same sub-list. |
|
1137 |
|
|
1138 |
This function modifies STRUCT." |
|
1139 |
(save-excursion |
|
1140 |
(let* ((end-A-no-blank (org-list-get-item-end-before-blank beg-A struct)) |
|
1141 |
(end-B-no-blank (org-list-get-item-end-before-blank beg-B struct)) |
|
1142 |
(end-A (org-list-get-item-end beg-A struct)) |
|
1143 |
(end-B (org-list-get-item-end beg-B struct)) |
|
1144 |
(size-A (- end-A-no-blank beg-A)) |
|
1145 |
(size-B (- end-B-no-blank beg-B)) |
|
1146 |
(body-A (buffer-substring beg-A end-A-no-blank)) |
|
1147 |
(body-B (buffer-substring beg-B end-B-no-blank)) |
|
1148 |
(between-A-no-blank-and-B (buffer-substring end-A-no-blank beg-B)) |
|
1149 |
(sub-A (cons beg-A (org-list-get-subtree beg-A struct))) |
|
1150 |
(sub-B (cons beg-B (org-list-get-subtree beg-B struct))) |
|
1151 |
;; Store overlays responsible for visibility status. We |
|
1152 |
;; also need to store their boundaries as they will be |
|
1153 |
;; removed from buffer. |
|
1154 |
(overlays |
|
1155 |
(cons |
|
1156 |
(delq nil |
|
1157 |
(mapcar (lambda (o) |
|
1158 |
(and (>= (overlay-start o) beg-A) |
|
1159 |
(<= (overlay-end o) end-A) |
|
1160 |
(list o (overlay-start o) (overlay-end o)))) |
|
1161 |
(overlays-in beg-A end-A))) |
|
1162 |
(delq nil |
|
1163 |
(mapcar (lambda (o) |
|
1164 |
(and (>= (overlay-start o) beg-B) |
|
1165 |
(<= (overlay-end o) end-B) |
|
1166 |
(list o (overlay-start o) (overlay-end o)))) |
|
1167 |
(overlays-in beg-B end-B)))))) |
|
1168 |
;; 1. Move effectively items in buffer. |
|
1169 |
(goto-char beg-A) |
|
1170 |
(delete-region beg-A end-B-no-blank) |
|
1171 |
(insert (concat body-B between-A-no-blank-and-B body-A)) |
|
1172 |
;; 2. Now modify struct. No need to re-read the list, the |
|
1173 |
;; transformation is just a shift of positions. Some special |
|
1174 |
;; attention is required for items ending at END-A and END-B |
|
1175 |
;; as empty spaces are not moved there. In others words, |
|
1176 |
;; item BEG-A will end with whitespaces that were at the end |
|
1177 |
;; of BEG-B and the same applies to BEG-B. |
|
1178 |
(dolist (e struct) |
|
1179 |
(let ((pos (car e))) |
|
1180 |
(cond |
|
1181 |
((< pos beg-A)) |
|
1182 |
((memq pos sub-A) |
|
1183 |
(let ((end-e (nth 6 e))) |
|
1184 |
(setcar e (+ pos (- end-B-no-blank end-A-no-blank))) |
|
1185 |
(setcar (nthcdr 6 e) |
|
1186 |
(+ end-e (- end-B-no-blank end-A-no-blank))) |
|
1187 |
(when (= end-e end-A) (setcar (nthcdr 6 e) end-B)))) |
|
1188 |
((memq pos sub-B) |
|
1189 |
(let ((end-e (nth 6 e))) |
|
1190 |
(setcar e (- (+ pos beg-A) beg-B)) |
|
1191 |
(setcar (nthcdr 6 e) (+ end-e (- beg-A beg-B))) |
|
1192 |
(when (= end-e end-B) |
|
1193 |
(setcar (nthcdr 6 e) |
|
1194 |
(+ beg-A size-B (- end-A end-A-no-blank)))))) |
|
1195 |
((< pos beg-B) |
|
1196 |
(let ((end-e (nth 6 e))) |
|
1197 |
(setcar e (+ pos (- size-B size-A))) |
|
1198 |
(setcar (nthcdr 6 e) (+ end-e (- size-B size-A)))))))) |
|
1199 |
(setq struct (sort struct #'car-less-than-car)) |
|
1200 |
;; Restore visibility status, by moving overlays to their new |
|
1201 |
;; position. |
|
1202 |
(dolist (ov (car overlays)) |
|
1203 |
(move-overlay |
|
1204 |
(car ov) |
|
1205 |
(+ (nth 1 ov) (- (+ beg-B (- size-B size-A)) beg-A)) |
|
1206 |
(+ (nth 2 ov) (- (+ beg-B (- size-B size-A)) beg-A)))) |
|
1207 |
(dolist (ov (cdr overlays)) |
|
1208 |
(move-overlay (car ov) |
|
1209 |
(+ (nth 1 ov) (- beg-A beg-B)) |
|
1210 |
(+ (nth 2 ov) (- beg-A beg-B)))) |
|
1211 |
;; Return structure. |
|
1212 |
struct))) |
|
1213 |
|
|
1214 |
(defun org-list-separating-blank-lines-number (pos struct prevs) |
|
1215 |
"Return number of blank lines that should separate items in list. |
|
1216 |
|
|
1217 |
POS is the position of point where `org-list-insert-item' was called. |
|
1218 |
|
|
1219 |
STRUCT is the list structure. PREVS is the alist of previous |
|
1220 |
items, as returned by `org-list-prevs-alist'. |
|
1221 |
|
|
1222 |
Assume point is at item's beginning. If the item is alone, apply |
|
1223 |
some heuristics to guess the result." |
|
1224 |
(save-excursion |
|
1225 |
(let ((item (point)) |
|
1226 |
(insert-blank-p |
|
1227 |
(cdr (assq 'plain-list-item org-blank-before-new-entry))) |
|
1228 |
usr-blank |
|
1229 |
(count-blanks |
|
1230 |
(function |
|
1231 |
(lambda () |
|
1232 |
;; Count blank lines above beginning of line. |
|
1233 |
(save-excursion |
|
1234 |
(count-lines (goto-char (point-at-bol)) |
|
1235 |
(progn (skip-chars-backward " \r\t\n") |
|
1236 |
(forward-line) |
|
1237 |
(point)))))))) |
|
1238 |
(cond |
|
1239 |
;; Trivial cases where there should be none. |
|
1240 |
((not insert-blank-p) 0) |
|
1241 |
;; When `org-blank-before-new-entry' says so, it is 1. |
|
1242 |
((eq insert-blank-p t) 1) |
|
1243 |
;; `plain-list-item' is 'auto. Count blank lines separating |
|
1244 |
;; neighbors' items in list. |
|
1245 |
(t (let ((next-p (org-list-get-next-item item struct prevs))) |
|
1246 |
(cond |
|
1247 |
;; Is there a next item? |
|
1248 |
(next-p (goto-char next-p) |
|
1249 |
(funcall count-blanks)) |
|
1250 |
;; Is there a previous item? |
|
1251 |
((org-list-get-prev-item item struct prevs) |
|
1252 |
(funcall count-blanks)) |
|
1253 |
;; User inserted blank lines, trust him. |
|
1254 |
((and (> pos (org-list-get-item-end-before-blank item struct)) |
|
1255 |
(> (save-excursion (goto-char pos) |
|
1256 |
(setq usr-blank (funcall count-blanks))) |
|
1257 |
0)) |
|
1258 |
usr-blank) |
|
1259 |
;; Are there blank lines inside the list so far? |
|
1260 |
((save-excursion |
|
1261 |
(goto-char (org-list-get-top-point struct)) |
|
1262 |
;; Do not use `org-list-search-forward' so blank lines |
|
1263 |
;; in blocks can be counted in. |
|
1264 |
(re-search-forward |
|
1265 |
"^[ \t]*$" (org-list-get-item-end-before-blank item struct) t)) |
|
1266 |
1) |
|
1267 |
;; Default choice: no blank line. |
|
1268 |
(t 0)))))))) |
|
1269 |
|
|
1270 |
(defun org-list-insert-item (pos struct prevs &optional checkbox after-bullet) |
|
1271 |
"Insert a new list item at POS and return the new structure. |
|
1272 |
If POS is before first character after bullet of the item, the |
|
1273 |
new item will be created before the current one. |
|
1274 |
|
|
1275 |
STRUCT is the list structure. PREVS is the alist of previous |
|
1276 |
items, as returned by `org-list-prevs-alist'. |
|
1277 |
|
|
1278 |
Insert a checkbox if CHECKBOX is non-nil, and string AFTER-BULLET |
|
1279 |
after the bullet. Cursor will be after this text once the |
|
1280 |
function ends. |
|
1281 |
|
|
1282 |
This function modifies STRUCT." |
|
1283 |
(let ((case-fold-search t)) |
|
1284 |
;; 1. Get information about list: position of point with regards |
|
1285 |
;; to item start (BEFOREP), blank lines number separating items |
|
1286 |
;; (BLANK-NB), if we're allowed to (SPLIT-LINE-P). |
|
1287 |
(let* ((item (progn (goto-char pos) (goto-char (org-list-get-item-begin)))) |
|
1288 |
(item-end (org-list-get-item-end item struct)) |
|
1289 |
(item-end-no-blank (org-list-get-item-end-before-blank item struct)) |
|
1290 |
(beforep |
|
1291 |
(progn |
|
1292 |
(looking-at org-list-full-item-re) |
|
1293 |
(<= pos |
|
1294 |
(cond |
|
1295 |
((not (match-beginning 4)) (match-end 0)) |
|
1296 |
;; Ignore tag in a non-descriptive list. |
|
1297 |
((save-match-data (string-match "[.)]" (match-string 1))) |
|
1298 |
(match-beginning 4)) |
|
1299 |
(t (save-excursion |
|
1300 |
(goto-char (match-end 4)) |
|
1301 |
(skip-chars-forward " \t") |
|
1302 |
(point))))))) |
|
1303 |
(split-line-p (org-get-alist-option org-M-RET-may-split-line 'item)) |
|
1304 |
(blank-nb (org-list-separating-blank-lines-number |
|
1305 |
pos struct prevs)) |
|
1306 |
;; 2. Build the new item to be created. Concatenate same |
|
1307 |
;; bullet as item, checkbox, text AFTER-BULLET if |
|
1308 |
;; provided, and text cut from point to end of item |
|
1309 |
;; (TEXT-CUT) to form item's BODY. TEXT-CUT depends on |
|
1310 |
;; BEFOREP and SPLIT-LINE-P. The difference of size |
|
1311 |
;; between what was cut and what was inserted in buffer |
|
1312 |
;; is stored in SIZE-OFFSET. |
|
1313 |
(ind (org-list-get-ind item struct)) |
|
1314 |
(ind-size (if indent-tabs-mode |
|
1315 |
(+ (/ ind tab-width) (mod ind tab-width)) |
|
1316 |
ind)) |
|
1317 |
(bullet (org-list-bullet-string (org-list-get-bullet item struct))) |
|
1318 |
(box (when checkbox "[ ]")) |
|
1319 |
(text-cut |
|
1320 |
(and (not beforep) split-line-p |
|
1321 |
(progn |
|
1322 |
(goto-char pos) |
|
1323 |
;; If POS is greater than ITEM-END, then point is |
|
1324 |
;; in some white lines after the end of the list. |
|
1325 |
;; Those must be removed, or they will be left, |
|
1326 |
;; stacking up after the list. |
|
1327 |
(when (< item-end pos) |
|
1328 |
(delete-region (1- item-end) (point-at-eol))) |
|
1329 |
(skip-chars-backward " \r\t\n") |
|
1330 |
(setq pos (point)) |
|
1331 |
(delete-and-extract-region pos item-end-no-blank)))) |
|
1332 |
(body (concat bullet (when box (concat box " ")) after-bullet |
|
1333 |
(and text-cut |
|
1334 |
(if (string-match "\\`[ \t]+" text-cut) |
|
1335 |
(replace-match "" t t text-cut) |
|
1336 |
text-cut)))) |
|
1337 |
(item-sep (make-string (1+ blank-nb) ?\n)) |
|
1338 |
(item-size (+ ind-size (length body) (length item-sep))) |
|
1339 |
(size-offset (- item-size (length text-cut)))) |
|
1340 |
;; 4. Insert effectively item into buffer. |
|
1341 |
(goto-char item) |
|
1342 |
(indent-to-column ind) |
|
1343 |
(insert body item-sep) |
|
1344 |
;; 5. Add new item to STRUCT. |
|
1345 |
(mapc (lambda (e) |
|
1346 |
(let ((p (car e)) (end (nth 6 e))) |
|
1347 |
(cond |
|
1348 |
;; Before inserted item, positions don't change but |
|
1349 |
;; an item ending after insertion has its end shifted |
|
1350 |
;; by SIZE-OFFSET. |
|
1351 |
((< p item) |
|
1352 |
(when (> end item) (setcar (nthcdr 6 e) (+ end size-offset)))) |
|
1353 |
;; Trivial cases where current item isn't split in |
|
1354 |
;; two. Just shift every item after new one by |
|
1355 |
;; ITEM-SIZE. |
|
1356 |
((or beforep (not split-line-p)) |
|
1357 |
(setcar e (+ p item-size)) |
|
1358 |
(setcar (nthcdr 6 e) (+ end item-size))) |
|
1359 |
;; Item is split in two: elements before POS are just |
|
1360 |
;; shifted by ITEM-SIZE. In the case item would end |
|
1361 |
;; after split POS, ending is only shifted by |
|
1362 |
;; SIZE-OFFSET. |
|
1363 |
((< p pos) |
|
1364 |
(setcar e (+ p item-size)) |
|
1365 |
(if (< end pos) |
|
1366 |
(setcar (nthcdr 6 e) (+ end item-size)) |
|
1367 |
(setcar (nthcdr 6 e) (+ end size-offset)))) |
|
1368 |
;; Elements after POS are moved into new item. |
|
1369 |
;; Length of ITEM-SEP has to be removed as ITEM-SEP |
|
1370 |
;; doesn't appear in buffer yet. |
|
1371 |
((< p item-end) |
|
1372 |
(setcar e (+ p size-offset (- item pos (length item-sep)))) |
|
1373 |
(if (= end item-end) |
|
1374 |
(setcar (nthcdr 6 e) (+ item item-size)) |
|
1375 |
(setcar (nthcdr 6 e) |
|
1376 |
(+ end size-offset |
|
1377 |
(- item pos (length item-sep)))))) |
|
1378 |
;; Elements at ITEM-END or after are only shifted by |
|
1379 |
;; SIZE-OFFSET. |
|
1380 |
(t (setcar e (+ p size-offset)) |
|
1381 |
(setcar (nthcdr 6 e) (+ end size-offset)))))) |
|
1382 |
struct) |
|
1383 |
(push (list item ind bullet nil box nil (+ item item-size)) struct) |
|
1384 |
(setq struct (sort struct (lambda (e1 e2) (< (car e1) (car e2))))) |
|
1385 |
;; 6. If not BEFOREP, new item must appear after ITEM, so |
|
1386 |
;; exchange ITEM with the next item in list. Position cursor |
|
1387 |
;; after bullet, counter, checkbox, and label. |
|
1388 |
(if beforep |
|
1389 |
(goto-char item) |
|
1390 |
(setq struct (org-list-swap-items item (+ item item-size) struct)) |
|
1391 |
(goto-char (org-list-get-next-item |
|
1392 |
item struct (org-list-prevs-alist struct)))) |
|
1393 |
struct))) |
|
1394 |
|
|
1395 |
(defun org-list-delete-item (item struct) |
|
1396 |
"Remove ITEM from the list and return the new structure. |
|
1397 |
|
|
1398 |
STRUCT is the list structure." |
|
1399 |
(let* ((end (org-list-get-item-end item struct)) |
|
1400 |
(beg (if (= (org-list-get-bottom-point struct) end) |
|
1401 |
;; If ITEM ends with the list, delete blank lines |
|
1402 |
;; before it. |
|
1403 |
(save-excursion |
|
1404 |
(goto-char item) |
|
1405 |
(skip-chars-backward " \r\t\n") |
|
1406 |
(min (1+ (point-at-eol)) (point-max))) |
|
1407 |
item))) |
|
1408 |
;; Remove item from buffer. |
|
1409 |
(delete-region beg end) |
|
1410 |
;; Remove item from structure and shift others items accordingly. |
|
1411 |
;; Don't forget to shift also ending position when appropriate. |
|
1412 |
(let ((size (- end beg))) |
|
1413 |
(delq nil (mapcar (lambda (e) |
|
1414 |
(let ((pos (car e))) |
|
1415 |
(cond |
|
1416 |
((< pos item) |
|
1417 |
(let ((end-e (nth 6 e))) |
|
1418 |
(cond |
|
1419 |
((< end-e item) e) |
|
1420 |
((= end-e item) |
|
1421 |
(append (butlast e) (list beg))) |
|
1422 |
(t |
|
1423 |
(append (butlast e) (list (- end-e size))))))) |
|
1424 |
((< pos end) nil) |
|
1425 |
(t |
|
1426 |
(cons (- pos size) |
|
1427 |
(append (butlast (cdr e)) |
|
1428 |
(list (- (nth 6 e) size)))))))) |
|
1429 |
struct))))) |
|
1430 |
|
|
1431 |
(defun org-list-send-item (item dest struct) |
|
1432 |
"Send ITEM to destination DEST. |
|
1433 |
|
|
1434 |
STRUCT is the list structure. |
|
1435 |
|
|
1436 |
DEST can have various values. |
|
1437 |
|
|
1438 |
If DEST is a buffer position, the function will assume it points |
|
1439 |
to another item in the same list as ITEM, and will move the |
|
1440 |
latter just before the former. |
|
1441 |
|
|
1442 |
If DEST is `begin' (respectively `end'), ITEM will be moved at |
|
1443 |
the beginning (respectively end) of the list it belongs to. |
|
1444 |
|
|
1445 |
If DEST is a string like \"N\", where N is an integer, ITEM will |
|
1446 |
be moved at the Nth position in the list. |
|
1447 |
|
|
1448 |
If DEST is `kill', ITEM will be deleted and its body will be |
|
1449 |
added to the kill-ring. |
|
1450 |
|
|
1451 |
If DEST is `delete', ITEM will be deleted. |
|
1452 |
|
|
1453 |
Visibility of item is preserved. |
|
1454 |
|
|
1455 |
This function returns, destructively, the new list structure." |
|
1456 |
(let* ((prevs (org-list-prevs-alist struct)) |
|
1457 |
(item-end (org-list-get-item-end item struct)) |
|
1458 |
;; Grab full item body minus its bullet. |
|
1459 |
(body (org-trim |
|
1460 |
(buffer-substring |
|
1461 |
(save-excursion |
|
1462 |
(goto-char item) |
|
1463 |
(looking-at |
|
1464 |
(concat "[ \t]*" |
|
1465 |
(regexp-quote (org-list-get-bullet item struct)))) |
|
1466 |
(match-end 0)) |
|
1467 |
item-end))) |
|
1468 |
;; Change DEST into a buffer position. A trick is needed |
|
1469 |
;; when ITEM is meant to be sent at the end of the list. |
|
1470 |
;; Indeed, by setting locally `org-M-RET-may-split-line' to |
|
1471 |
;; nil and insertion point (INS-POINT) to the first line's |
|
1472 |
;; end of the last item, we ensure the new item will be |
|
1473 |
;; inserted after the last item, and not after any of its |
|
1474 |
;; hypothetical sub-items. |
|
1475 |
(ins-point (cond |
|
1476 |
((or (eq dest 'kill) (eq dest 'delete))) |
|
1477 |
((eq dest 'begin) |
|
1478 |
(setq dest (org-list-get-list-begin item struct prevs))) |
|
1479 |
((eq dest 'end) |
|
1480 |
(setq dest (org-list-get-list-end item struct prevs)) |
|
1481 |
(save-excursion |
|
1482 |
(goto-char (org-list-get-last-item item struct prevs)) |
|
1483 |
(point-at-eol))) |
|
1484 |
((string-match-p "\\`[0-9]+\\'" dest) |
|
1485 |
(let* ((all (org-list-get-all-items item struct prevs)) |
|
1486 |
(len (length all)) |
|
1487 |
(index (mod (string-to-number dest) len))) |
|
1488 |
(if (not (zerop index)) |
|
1489 |
(setq dest (nth (1- index) all)) |
|
1490 |
;; Send ITEM at the end of the list. |
|
1491 |
(setq dest (org-list-get-list-end item struct prevs)) |
|
1492 |
(save-excursion |
|
1493 |
(goto-char |
|
1494 |
(org-list-get-last-item item struct prevs)) |
|
1495 |
(point-at-eol))))) |
|
1496 |
(t dest))) |
|
1497 |
(org-M-RET-may-split-line nil) |
|
1498 |
;; Store inner overlays (to preserve visibility). |
|
1499 |
(overlays (cl-remove-if (lambda (o) (or (< (overlay-start o) item) |
|
1500 |
(> (overlay-end o) item))) |
|
1501 |
(overlays-in item item-end)))) |
|
1502 |
(cond |
|
1503 |
((eq dest 'delete) (org-list-delete-item item struct)) |
|
1504 |
((eq dest 'kill) |
|
1505 |
(kill-new body) |
|
1506 |
(org-list-delete-item item struct)) |
|
1507 |
((and (integerp dest) (/= item ins-point)) |
|
1508 |
(setq item (copy-marker item)) |
|
1509 |
(setq struct (org-list-insert-item ins-point struct prevs nil body)) |
|
1510 |
;; 1. Structure returned by `org-list-insert-item' may not be |
|
1511 |
;; accurate, as it cannot see sub-items included in BODY. |
|
1512 |
;; Thus, first compute the real structure so far. |
|
1513 |
(let ((moved-items |
|
1514 |
(cons (marker-position item) |
|
1515 |
(org-list-get-subtree (marker-position item) struct))) |
|
1516 |
(new-end (org-list-get-item-end (point) struct)) |
|
1517 |
(old-end (org-list-get-item-end (marker-position item) struct)) |
|
1518 |
(new-item (point)) |
|
1519 |
(shift (- (point) item))) |
|
1520 |
;; 1.1. Remove the item just created in structure. |
|
1521 |
(setq struct (delete (assq new-item struct) struct)) |
|
1522 |
;; 1.2. Copy ITEM and any of its sub-items at NEW-ITEM. |
|
1523 |
(setq struct (sort |
|
1524 |
(append |
|
1525 |
struct |
|
1526 |
(mapcar (lambda (e) |
|
1527 |
(let* ((cell (assq e struct)) |
|
1528 |
(pos (car cell)) |
|
1529 |
(end (nth 6 cell))) |
|
1530 |
(cons (+ pos shift) |
|
1531 |
(append (butlast (cdr cell)) |
|
1532 |
(list (if (= end old-end) |
|
1533 |
new-end |
|
1534 |
(+ end shift))))))) |
|
1535 |
moved-items)) |
|
1536 |
#'car-less-than-car))) |
|
1537 |
;; 2. Restore inner overlays. |
|
1538 |
(dolist (o overlays) |
|
1539 |
(move-overlay o |
|
1540 |
(+ (overlay-start o) (- (point) item)) |
|
1541 |
(+ (overlay-end o) (- (point) item)))) |
|
1542 |
;; 3. Eventually delete extra copy of the item and clean marker. |
|
1543 |
(prog1 (org-list-delete-item (marker-position item) struct) |
|
1544 |
(move-marker item nil))) |
|
1545 |
(t struct)))) |
|
1546 |
|
|
1547 |
(defun org-list-struct-outdent (start end struct parents) |
|
1548 |
"Outdent items between positions START and END. |
|
1549 |
|
|
1550 |
STRUCT is the list structure. PARENTS is the alist of items' |
|
1551 |
parents, as returned by `org-list-parents-alist'. |
|
1552 |
|
|
1553 |
START is included, END excluded." |
|
1554 |
(let* (acc |
|
1555 |
(out (lambda (cell) |
|
1556 |
(let* ((item (car cell)) |
|
1557 |
(parent (cdr cell))) |
|
1558 |
(cond |
|
1559 |
;; Item not yet in zone: keep association. |
|
1560 |
((< item start) cell) |
|
1561 |
;; Item out of zone: follow associations in ACC. |
|
1562 |
((>= item end) |
|
1563 |
(let ((convert (and parent (assq parent acc)))) |
|
1564 |
(if convert (cons item (cdr convert)) cell))) |
|
1565 |
;; Item has no parent: error |
|
1566 |
((not parent) |
|
1567 |
(error "Cannot outdent top-level items")) |
|
1568 |
;; Parent is outdented: keep association. |
|
1569 |
((>= parent start) |
|
1570 |
(push (cons parent item) acc) cell) |
|
1571 |
(t |
|
1572 |
;; Parent isn't outdented: reparent to grand-parent. |
|
1573 |
(let ((grand-parent (org-list-get-parent |
|
1574 |
parent struct parents))) |
|
1575 |
(push (cons parent item) acc) |
|
1576 |
(cons item grand-parent)))))))) |
|
1577 |
(mapcar out parents))) |
|
1578 |
|
|
1579 |
(defun org-list-struct-indent (start end struct parents prevs) |
|
1580 |
"Indent items between positions START and END. |
|
1581 |
|
|
1582 |
STRUCT is the list structure. PARENTS is the alist of parents |
|
1583 |
and PREVS is the alist of previous items, returned by, |
|
1584 |
respectively, `org-list-parents-alist' and |
|
1585 |
`org-list-prevs-alist'. |
|
1586 |
|
|
1587 |
START is included and END excluded. |
|
1588 |
|
|
1589 |
STRUCT may be modified if `org-list-demote-modify-bullet' matches |
|
1590 |
bullets between START and END." |
|
1591 |
(let* (acc |
|
1592 |
(set-assoc (lambda (cell) (push cell acc) cell)) |
|
1593 |
(change-bullet-maybe |
|
1594 |
(function |
|
1595 |
(lambda (item) |
|
1596 |
(let ((new-bul-p |
|
1597 |
(cdr (assoc |
|
1598 |
;; Normalize ordered bullets. |
|
1599 |
(let ((bul (org-trim |
|
1600 |
(org-list-get-bullet item struct)))) |
|
1601 |
(cond ((string-match "[A-Z]\\." bul) "A.") |
|
1602 |
((string-match "[A-Z])" bul) "A)") |
|
1603 |
((string-match "[a-z]\\." bul) "a.") |
|
1604 |
((string-match "[a-z])" bul) "a)") |
|
1605 |
((string-match "[0-9]\\." bul) "1.") |
|
1606 |
((string-match "[0-9])" bul) "1)") |
|
1607 |
(t bul))) |
|
1608 |
org-list-demote-modify-bullet)))) |
|
1609 |
(when new-bul-p (org-list-set-bullet item struct new-bul-p)))))) |
|
1610 |
(ind |
|
1611 |
(lambda (cell) |
|
1612 |
(let* ((item (car cell)) |
|
1613 |
(parent (cdr cell))) |
|
1614 |
(cond |
|
1615 |
;; Item not yet in zone: keep association. |
|
1616 |
((< item start) cell) |
|
1617 |
((>= item end) |
|
1618 |
;; Item out of zone: follow associations in ACC. |
|
1619 |
(let ((convert (assq parent acc))) |
|
1620 |
(if convert (cons item (cdr convert)) cell))) |
|
1621 |
(t |
|
1622 |
;; Item is in zone... |
|
1623 |
(let ((prev (org-list-get-prev-item item struct prevs))) |
|
1624 |
;; Check if bullet needs to be changed. |
|
1625 |
(funcall change-bullet-maybe item) |
|
1626 |
(cond |
|
1627 |
;; First item indented but not parent: error |
|
1628 |
((and (not prev) (or (not parent) (< parent start))) |
|
1629 |
(user-error "Cannot indent the first item of a list")) |
|
1630 |
;; First item and parent indented: keep same |
|
1631 |
;; parent. |
|
1632 |
((not prev) (funcall set-assoc cell)) |
|
1633 |
;; Previous item not indented: reparent to it. |
|
1634 |
((< prev start) (funcall set-assoc (cons item prev))) |
|
1635 |
;; Previous item indented: reparent like it. |
|
1636 |
(t |
|
1637 |
(funcall set-assoc |
|
1638 |
(cons item (cdr (assq prev acc))))))))))))) |
|
1639 |
(mapcar ind parents))) |
|
1640 |
|
|
1641 |
|
|
1642 |
|
|
1643 |
;;; Repairing structures |
|
1644 |
|
|
1645 |
(defun org-list-use-alpha-bul-p (first struct prevs) |
|
1646 |
"Non-nil if list starting at FIRST can have alphabetical bullets. |
|
1647 |
|
|
1648 |
STRUCT is list structure. PREVS is the alist of previous items, |
|
1649 |
as returned by `org-list-prevs-alist'." |
|
1650 |
(and org-list-allow-alphabetical |
|
1651 |
(catch 'exit |
|
1652 |
(let ((item first) (ascii 64) (case-fold-search nil)) |
|
1653 |
;; Pretend that bullets are uppercase and check if alphabet |
|
1654 |
;; is sufficient, taking counters into account. |
|
1655 |
(while item |
|
1656 |
(let ((count (org-list-get-counter item struct))) |
|
1657 |
;; Virtually determine current bullet |
|
1658 |
(if (and count (string-match-p "[a-zA-Z]" count)) |
|
1659 |
;; Counters are not case-sensitive. |
|
1660 |
(setq ascii (string-to-char (upcase count))) |
|
1661 |
(setq ascii (1+ ascii))) |
|
1662 |
;; Test if bullet would be over z or Z. |
|
1663 |
(if (> ascii 90) |
|
1664 |
(throw 'exit nil) |
|
1665 |
(setq item (org-list-get-next-item item struct prevs))))) |
|
1666 |
;; All items checked. All good. |
|
1667 |
t)))) |
|
1668 |
|
|
1669 |
(defun org-list-inc-bullet-maybe (bullet) |
|
1670 |
"Increment BULLET if applicable." |
|
1671 |
(let ((case-fold-search nil)) |
|
1672 |
(cond |
|
1673 |
;; Num bullet: increment it. |
|
1674 |
((string-match "[0-9]+" bullet) |
|
1675 |
(replace-match |
|
1676 |
(number-to-string (1+ (string-to-number (match-string 0 bullet)))) |
|
1677 |
nil nil bullet)) |
|
1678 |
;; Alpha bullet: increment it. |
|
1679 |
((string-match "[A-Za-z]" bullet) |
|
1680 |
(replace-match |
|
1681 |
(char-to-string (1+ (string-to-char (match-string 0 bullet)))) |
|
1682 |
nil nil bullet)) |
|
1683 |
;; Unordered bullet: leave it. |
|
1684 |
(t bullet)))) |
|
1685 |
|
|
1686 |
(defun org-list-struct-fix-bul (struct prevs) |
|
1687 |
"Verify and correct bullets in STRUCT. |
|
1688 |
PREVS is the alist of previous items, as returned by |
|
1689 |
`org-list-prevs-alist'. |
|
1690 |
|
|
1691 |
This function modifies STRUCT." |
|
1692 |
(let ((case-fold-search nil) |
|
1693 |
(fix-bul |
|
1694 |
(function |
|
1695 |
;; Set bullet of ITEM in STRUCT, depending on the type of |
|
1696 |
;; first item of the list, the previous bullet and counter |
|
1697 |
;; if any. |
|
1698 |
(lambda (item) |
|
1699 |
(let* ((prev (org-list-get-prev-item item struct prevs)) |
|
1700 |
(prev-bul (and prev (org-list-get-bullet prev struct))) |
|
1701 |
(counter (org-list-get-counter item struct)) |
|
1702 |
(bullet (org-list-get-bullet item struct)) |
|
1703 |
(alphap (and (not prev) |
|
1704 |
(org-list-use-alpha-bul-p item struct prevs)))) |
|
1705 |
(org-list-set-bullet |
|
1706 |
item struct |
|
1707 |
(org-list-bullet-string |
|
1708 |
(cond |
|
1709 |
;; Alpha counter in alpha list: use counter. |
|
1710 |
((and prev counter |
|
1711 |
(string-match "[a-zA-Z]" counter) |
|
1712 |
(string-match "[a-zA-Z]" prev-bul)) |
|
1713 |
;; Use cond to be sure `string-match' is used in |
|
1714 |
;; both cases. |
|
1715 |
(let ((real-count |
|
1716 |
(cond |
|
1717 |
((string-match "[a-z]" prev-bul) (downcase counter)) |
|
1718 |
((string-match "[A-Z]" prev-bul) (upcase counter))))) |
|
1719 |
(replace-match real-count nil nil prev-bul))) |
|
1720 |
;; Num counter in a num list: use counter. |
|
1721 |
((and prev counter |
|
1722 |
(string-match "[0-9]+" counter) |
|
1723 |
(string-match "[0-9]+" prev-bul)) |
|
1724 |
(replace-match counter nil nil prev-bul)) |
|
1725 |
;; No counter: increase, if needed, previous bullet. |
|
1726 |
(prev |
|
1727 |
(org-list-inc-bullet-maybe (org-list-get-bullet prev struct))) |
|
1728 |
;; Alpha counter at first item: use counter. |
|
1729 |
((and counter (org-list-use-alpha-bul-p item struct prevs) |
|
1730 |
(string-match "[A-Za-z]" counter) |
|
1731 |
(string-match "[A-Za-z]" bullet)) |
|
1732 |
(let ((real-count |
|
1733 |
(cond |
|
1734 |
((string-match "[a-z]" bullet) (downcase counter)) |
|
1735 |
((string-match "[A-Z]" bullet) (upcase counter))))) |
|
1736 |
(replace-match real-count nil nil bullet))) |
|
1737 |
;; Num counter at first item: use counter. |
|
1738 |
((and counter |
|
1739 |
(string-match "[0-9]+" counter) |
|
1740 |
(string-match "[0-9]+" bullet)) |
|
1741 |
(replace-match counter nil nil bullet)) |
|
1742 |
;; First bullet is alpha uppercase: use "A". |
|
1743 |
((and alphap (string-match "[A-Z]" bullet)) |
|
1744 |
(replace-match "A" nil nil bullet)) |
|
1745 |
;; First bullet is alpha lowercase: use "a". |
|
1746 |
((and alphap (string-match "[a-z]" bullet)) |
|
1747 |
(replace-match "a" nil nil bullet)) |
|
1748 |
;; First bullet is num: use "1". |
|
1749 |
((string-match "\\([0-9]+\\|[A-Za-z]\\)" bullet) |
|
1750 |
(replace-match "1" nil nil bullet)) |
|
1751 |
;; Not an ordered list: keep bullet. |
|
1752 |
(t bullet))))))))) |
|
1753 |
(mapc fix-bul (mapcar #'car struct)))) |
|
1754 |
|
|
1755 |
(defun org-list-struct-fix-ind (struct parents &optional bullet-size) |
|
1756 |
"Verify and correct indentation in STRUCT. |
|
1757 |
|
|
1758 |
PARENTS is the alist of parents, as returned by |
|
1759 |
`org-list-parents-alist'. |
|
1760 |
|
|
1761 |
If numeric optional argument BULLET-SIZE is set, assume all |
|
1762 |
bullets in list have this length to determine new indentation. |
|
1763 |
|
|
1764 |
This function modifies STRUCT." |
|
1765 |
(let* ((ancestor (org-list-get-top-point struct)) |
|
1766 |
(top-ind (org-list-get-ind ancestor struct)) |
|
1767 |
(new-ind |
|
1768 |
(lambda (item) |
|
1769 |
(let ((parent (org-list-get-parent item struct parents))) |
|
1770 |
(if parent |
|
1771 |
;; Indent like parent + length of parent's bullet + |
|
1772 |
;; sub-list offset. |
|
1773 |
(org-list-set-ind |
|
1774 |
item struct (+ (or bullet-size |
|
1775 |
(length |
|
1776 |
(org-list-get-bullet parent struct))) |
|
1777 |
(org-list-get-ind parent struct) |
|
1778 |
org-list-indent-offset)) |
|
1779 |
;; If no parent, indent like top-point. |
|
1780 |
(org-list-set-ind item struct top-ind)))))) |
|
1781 |
(mapc new-ind (mapcar #'car (cdr struct))))) |
|
1782 |
|
|
1783 |
(defun org-list-struct-fix-box (struct parents prevs &optional ordered) |
|
1784 |
"Verify and correct checkboxes in STRUCT. |
|
1785 |
|
|
1786 |
PARENTS is the alist of parents and PREVS is the alist of |
|
1787 |
previous items, as returned by, respectively, |
|
1788 |
`org-list-parents-alist' and `org-list-prevs-alist'. |
|
1789 |
|
|
1790 |
If ORDERED is non-nil, a checkbox can only be checked when every |
|
1791 |
checkbox before it is checked too. If there was an attempt to |
|
1792 |
break this rule, the function will return the blocking item. In |
|
1793 |
all others cases, the return value will be nil. |
|
1794 |
|
|
1795 |
This function modifies STRUCT." |
|
1796 |
(let ((all-items (mapcar #'car struct)) |
|
1797 |
(set-parent-box |
|
1798 |
(function |
|
1799 |
(lambda (item) |
|
1800 |
(let* ((box-list |
|
1801 |
(mapcar (lambda (child) |
|
1802 |
(org-list-get-checkbox child struct)) |
|
1803 |
(org-list-get-children item struct parents)))) |
|
1804 |
(org-list-set-checkbox |
|
1805 |
item struct |
|
1806 |
(cond |
|
1807 |
((and (member "[ ]" box-list) (member "[X]" box-list)) "[-]") |
|
1808 |
((member "[-]" box-list) "[-]") |
|
1809 |
((member "[X]" box-list) "[X]") |
|
1810 |
((member "[ ]" box-list) "[ ]") |
|
1811 |
;; Parent has no boxed child: leave box as-is. |
|
1812 |
(t (org-list-get-checkbox item struct)))))))) |
|
1813 |
parent-list) |
|
1814 |
;; 1. List all parents with a checkbox. |
|
1815 |
(mapc |
|
1816 |
(lambda (e) |
|
1817 |
(let* ((parent (org-list-get-parent e struct parents)) |
|
1818 |
(parent-box-p (org-list-get-checkbox parent struct))) |
|
1819 |
(when (and parent-box-p (not (memq parent parent-list))) |
|
1820 |
(push parent parent-list)))) |
|
1821 |
all-items) |
|
1822 |
;; 2. Sort those parents by decreasing indentation. |
|
1823 |
(setq parent-list (sort parent-list |
|
1824 |
(lambda (e1 e2) |
|
1825 |
(> (org-list-get-ind e1 struct) |
|
1826 |
(org-list-get-ind e2 struct))))) |
|
1827 |
;; 3. For each parent, get all children's checkboxes to determine |
|
1828 |
;; and set its checkbox accordingly. |
|
1829 |
(mapc set-parent-box parent-list) |
|
1830 |
;; 4. If ORDERED is set, see if we need to uncheck some boxes. |
|
1831 |
(when ordered |
|
1832 |
(let* ((box-list |
|
1833 |
(mapcar (lambda (e) (org-list-get-checkbox e struct)) all-items)) |
|
1834 |
(after-unchecked (member "[ ]" box-list))) |
|
1835 |
;; There are boxes checked after an unchecked one: fix that. |
|
1836 |
(when (member "[X]" after-unchecked) |
|
1837 |
(let ((index (- (length struct) (length after-unchecked)))) |
|
1838 |
(mapc (lambda (e) |
|
1839 |
(when (org-list-get-checkbox e struct) |
|
1840 |
(org-list-set-checkbox e struct "[ ]"))) |
|
1841 |
(nthcdr index all-items)) |
|
1842 |
;; Verify once again the structure, without ORDERED. |
|
1843 |
(org-list-struct-fix-box struct parents prevs nil) |
|
1844 |
;; Return blocking item. |
|
1845 |
(nth index all-items))))))) |
|
1846 |
|
|
1847 |
(defun org-list-struct-fix-item-end (struct) |
|
1848 |
"Verify and correct each item end position in STRUCT. |
|
1849 |
|
|
1850 |
This function modifies STRUCT." |
|
1851 |
(let (end-list acc-end) |
|
1852 |
(mapc (lambda (e) |
|
1853 |
(let* ((pos (car e)) |
|
1854 |
(ind-pos (org-list-get-ind pos struct)) |
|
1855 |
(end-pos (org-list-get-item-end pos struct))) |
|
1856 |
(unless (assq end-pos struct) |
|
1857 |
;; To determine real ind of an ending position that is |
|
1858 |
;; not at an item, we have to find the item it belongs |
|
1859 |
;; to: it is the last item (ITEM-UP), whose ending is |
|
1860 |
;; further than the position we're interested in. |
|
1861 |
(let ((item-up (assoc-default end-pos acc-end '>))) |
|
1862 |
(push (cons |
|
1863 |
;; Else part is for the bottom point. |
|
1864 |
(if item-up (+ (org-list-get-ind item-up struct) 2) 0) |
|
1865 |
end-pos) |
|
1866 |
end-list))) |
|
1867 |
(push (cons ind-pos pos) end-list) |
|
1868 |
(push (cons end-pos pos) acc-end))) |
|
1869 |
struct) |
|
1870 |
(setq end-list (sort end-list (lambda (e1 e2) (< (cdr e1) (cdr e2))))) |
|
1871 |
(org-list-struct-assoc-end struct end-list))) |
|
1872 |
|
|
1873 |
(defun org-list-struct-apply-struct (struct old-struct) |
|
1874 |
"Apply set difference between STRUCT and OLD-STRUCT to the buffer. |
|
1875 |
|
|
1876 |
OLD-STRUCT is the structure before any modifications, and STRUCT |
|
1877 |
the structure to be applied. The function will only modify parts |
|
1878 |
of the list which have changed. |
|
1879 |
|
|
1880 |
Initial position of cursor is restored after the changes." |
|
1881 |
(let* ((origin (point-marker)) |
|
1882 |
(inlinetask-re (and (featurep 'org-inlinetask) |
|
1883 |
(org-inlinetask-outline-regexp))) |
|
1884 |
(item-re (org-item-re)) |
|
1885 |
(shift-body-ind |
|
1886 |
(function |
|
1887 |
;; Shift the indentation between END and BEG by DELTA. |
|
1888 |
;; Start from the line before END. |
|
1889 |
(lambda (end beg delta) |
|
1890 |
(goto-char end) |
|
1891 |
(skip-chars-backward " \r\t\n") |
|
1892 |
(beginning-of-line) |
|
1893 |
(while (or (> (point) beg) |
|
1894 |
(and (= (point) beg) |
|
1895 |
(not (looking-at item-re)))) |
|
1896 |
(cond |
|
1897 |
;; Skip inline tasks. |
|
1898 |
((and inlinetask-re (looking-at inlinetask-re)) |
|
1899 |
(org-inlinetask-goto-beginning)) |
|
1900 |
;; Shift only non-empty lines. |
|
1901 |
((looking-at-p "^[ \t]*\\S-") |
|
1902 |
(indent-line-to (+ (org-get-indentation) delta)))) |
|
1903 |
(forward-line -1))))) |
|
1904 |
(modify-item |
|
1905 |
(function |
|
1906 |
;; Replace ITEM first line elements with new elements from |
|
1907 |
;; STRUCT, if appropriate. |
|
1908 |
(lambda (item) |
|
1909 |
(goto-char item) |
|
1910 |
(let* ((new-ind (org-list-get-ind item struct)) |
|
1911 |
(old-ind (org-get-indentation)) |
|
1912 |
(new-bul (org-list-bullet-string |
|
1913 |
(org-list-get-bullet item struct))) |
|
1914 |
(old-bul (org-list-get-bullet item old-struct)) |
|
1915 |
(new-box (org-list-get-checkbox item struct))) |
|
1916 |
(looking-at org-list-full-item-re) |
|
1917 |
;; a. Replace bullet |
|
1918 |
(unless (equal old-bul new-bul) |
|
1919 |
(replace-match new-bul nil nil nil 1)) |
|
1920 |
;; b. Replace checkbox. |
|
1921 |
(cond |
|
1922 |
((equal (match-string 3) new-box)) |
|
1923 |
((and (match-string 3) new-box) |
|
1924 |
(replace-match new-box nil nil nil 3)) |
|
1925 |
((match-string 3) |
|
1926 |
(looking-at ".*?\\([ \t]*\\[[ X-]\\]\\)") |
|
1927 |
(replace-match "" nil nil nil 1)) |
|
1928 |
(t (let ((counterp (match-end 2))) |
|
1929 |
(goto-char (if counterp (1+ counterp) (match-end 1))) |
|
1930 |
(insert (concat new-box (unless counterp " ")))))) |
|
1931 |
;; c. Indent item to appropriate column. |
|
1932 |
(unless (= new-ind old-ind) |
|
1933 |
(delete-region (goto-char (point-at-bol)) |
|
1934 |
(progn (skip-chars-forward " \t") (point))) |
|
1935 |
(indent-to new-ind))))))) |
|
1936 |
;; 1. First get list of items and position endings. We maintain |
|
1937 |
;; two alists: ITM-SHIFT, determining indentation shift needed |
|
1938 |
;; at item, and END-LIST, a pseudo-alist where key is ending |
|
1939 |
;; position and value point. |
|
1940 |
(let (end-list acc-end itm-shift all-ends sliced-struct) |
|
1941 |
(dolist (e old-struct) |
|
1942 |
(let* ((pos (car e)) |
|
1943 |
(ind-pos (org-list-get-ind pos struct)) |
|
1944 |
(ind-old (org-list-get-ind pos old-struct)) |
|
1945 |
(bul-pos (org-list-get-bullet pos struct)) |
|
1946 |
(bul-old (org-list-get-bullet pos old-struct)) |
|
1947 |
(ind-shift (- (+ ind-pos (length bul-pos)) |
|
1948 |
(+ ind-old (length bul-old)))) |
|
1949 |
(end-pos (org-list-get-item-end pos old-struct))) |
|
1950 |
(push (cons pos ind-shift) itm-shift) |
|
1951 |
(unless (assq end-pos old-struct) |
|
1952 |
;; To determine real ind of an ending position that |
|
1953 |
;; is not at an item, we have to find the item it |
|
1954 |
;; belongs to: it is the last item (ITEM-UP), whose |
|
1955 |
;; ending is further than the position we're |
|
1956 |
;; interested in. |
|
1957 |
(let ((item-up (assoc-default end-pos acc-end #'>))) |
|
1958 |
(push (cons end-pos item-up) end-list))) |
|
1959 |
(push (cons end-pos pos) acc-end))) |
|
1960 |
;; 2. Slice the items into parts that should be shifted by the |
|
1961 |
;; same amount of indentation. Each slice follow the pattern |
|
1962 |
;; (END BEG DELTA). Slices are returned in reverse order. |
|
1963 |
(setq all-ends (sort (append (mapcar #'car itm-shift) |
|
1964 |
(org-uniquify (mapcar #'car end-list))) |
|
1965 |
#'<) |
|
1966 |
acc-end (nreverse acc-end)) |
|
1967 |
(while (cdr all-ends) |
|
1968 |
(let* ((up (pop all-ends)) |
|
1969 |
(down (car all-ends)) |
|
1970 |
(itemp (assq up struct)) |
|
1971 |
(delta |
|
1972 |
(if itemp (cdr (assq up itm-shift)) |
|
1973 |
;; If we're not at an item, there's a child of the |
|
1974 |
;; item point belongs to above. Make sure the less |
|
1975 |
;; indented line in this slice has the same column |
|
1976 |
;; as that child. |
|
1977 |
(let* ((child (cdr (assq up acc-end))) |
|
1978 |
(ind (org-list-get-ind child struct)) |
|
1979 |
(min-ind most-positive-fixnum)) |
|
1980 |
(save-excursion |
|
1981 |
(goto-char up) |
|
1982 |
(while (< (point) down) |
|
1983 |
;; Ignore empty lines. Also ignore blocks and |
|
1984 |
;; drawers contents. |
|
1985 |
(unless (looking-at-p "[ \t]*$") |
|
1986 |
(setq min-ind (min (org-get-indentation) min-ind)) |
|
1987 |
(cond |
|
1988 |
((and (looking-at "#\\+BEGIN\\(:\\|_\\S-+\\)") |
|
1989 |
(re-search-forward |
|
1990 |
(format "^[ \t]*#\\+END%s[ \t]*$" |
|
1991 |
(match-string 1)) |
|
1992 |
down t))) |
|
1993 |
((and (looking-at org-drawer-regexp) |
|
1994 |
(re-search-forward "^[ \t]*:END:[ \t]*$" |
|
1995 |
down t))))) |
|
1996 |
(forward-line))) |
|
1997 |
(- ind min-ind))))) |
|
1998 |
(push (list down up delta) sliced-struct))) |
|
1999 |
;; 3. Shift each slice in buffer, provided delta isn't 0, from |
|
2000 |
;; end to beginning. Take a special action when beginning is |
|
2001 |
;; at item bullet. |
|
2002 |
(dolist (e sliced-struct) |
|
2003 |
(unless (zerop (nth 2 e)) (apply shift-body-ind e)) |
|
2004 |
(let* ((beg (nth 1 e)) |
|
2005 |
(cell (assq beg struct))) |
|
2006 |
(unless (or (not cell) (equal cell (assq beg old-struct))) |
|
2007 |
(funcall modify-item beg))))) |
|
2008 |
;; 4. Go back to initial position and clean marker. |
|
2009 |
(goto-char origin) |
|
2010 |
(move-marker origin nil))) |
|
2011 |
|
|
2012 |
(defun org-list-write-struct (struct parents &optional old-struct) |
|
2013 |
"Correct bullets, checkboxes and indentation in list at point. |
|
2014 |
|
|
2015 |
STRUCT is the list structure. PARENTS is the alist of parents, |
|
2016 |
as returned by `org-list-parents-alist'. |
|
2017 |
|
|
2018 |
When non-nil, optional argument OLD-STRUCT is the reference |
|
2019 |
structure of the list. It should be provided whenever STRUCT |
|
2020 |
doesn't correspond anymore to the real list in buffer." |
|
2021 |
;; Order of functions matters here: checkboxes and endings need |
|
2022 |
;; correct indentation to be set, and indentation needs correct |
|
2023 |
;; bullets. |
|
2024 |
;; |
|
2025 |
;; 0. Save a copy of structure before modifications |
|
2026 |
(let ((old-struct (or old-struct (copy-tree struct)))) |
|
2027 |
;; 1. Set a temporary, but coherent with PARENTS, indentation in |
|
2028 |
;; order to get items endings and bullets properly |
|
2029 |
(org-list-struct-fix-ind struct parents 2) |
|
2030 |
;; 2. Fix each item end to get correct prevs alist. |
|
2031 |
(org-list-struct-fix-item-end struct) |
|
2032 |
;; 3. Get bullets right. |
|
2033 |
(let ((prevs (org-list-prevs-alist struct))) |
|
2034 |
(org-list-struct-fix-bul struct prevs) |
|
2035 |
;; 4. Now get real indentation. |
|
2036 |
(org-list-struct-fix-ind struct parents) |
|
2037 |
;; 5. Eventually fix checkboxes. |
|
2038 |
(org-list-struct-fix-box struct parents prevs)) |
|
2039 |
;; 6. Apply structure modifications to buffer. |
|
2040 |
(org-list-struct-apply-struct struct old-struct))) |
|
2041 |
|
|
2042 |
|
|
2043 |
|
|
2044 |
;;; Misc Tools |
|
2045 |
|
|
2046 |
(defun org-apply-on-list (function init-value &rest args) |
|
2047 |
"Call FUNCTION on each item of the list at point. |
|
2048 |
FUNCTION must be called with at least one argument: INIT-VALUE, |
|
2049 |
that will contain the value returned by the function at the |
|
2050 |
previous item, plus ARGS extra arguments. |
|
2051 |
|
|
2052 |
FUNCTION is applied on items in reverse order. |
|
2053 |
|
|
2054 |
As an example, \(org-apply-on-list \(lambda \(result) \(1+ result)) 0) |
|
2055 |
will return the number of items in the current list. |
|
2056 |
|
|
2057 |
Sublists of the list are skipped. Cursor is always at the |
|
2058 |
beginning of the item." |
|
2059 |
(let* ((struct (org-list-struct)) |
|
2060 |
(prevs (org-list-prevs-alist struct)) |
|
2061 |
(item (copy-marker (point-at-bol))) |
|
2062 |
(all (org-list-get-all-items (marker-position item) struct prevs)) |
|
2063 |
(value init-value)) |
|
2064 |
(mapc (lambda (e) |
|
2065 |
(goto-char e) |
|
2066 |
(setq value (apply function value args))) |
|
2067 |
(nreverse all)) |
|
2068 |
(goto-char item) |
|
2069 |
(move-marker item nil) |
|
2070 |
value)) |
|
2071 |
|
|
2072 |
(defun org-list-set-item-visibility (item struct view) |
|
2073 |
"Set visibility of ITEM in STRUCT to VIEW. |
|
2074 |
|
|
2075 |
Possible values are: `folded', `children' or `subtree'. See |
|
2076 |
`org-cycle' for more information." |
|
2077 |
(cond |
|
2078 |
((eq view 'folded) |
|
2079 |
(let ((item-end (org-list-get-item-end-before-blank item struct))) |
|
2080 |
;; Hide from eol |
|
2081 |
(outline-flag-region (save-excursion (goto-char item) (point-at-eol)) |
|
2082 |
item-end t))) |
|
2083 |
((eq view 'children) |
|
2084 |
;; First show everything. |
|
2085 |
(org-list-set-item-visibility item struct 'subtree) |
|
2086 |
;; Then fold every child. |
|
2087 |
(let* ((parents (org-list-parents-alist struct)) |
|
2088 |
(children (org-list-get-children item struct parents))) |
|
2089 |
(mapc (lambda (e) |
|
2090 |
(org-list-set-item-visibility e struct 'folded)) |
|
2091 |
children))) |
|
2092 |
((eq view 'subtree) |
|
2093 |
;; Show everything |
|
2094 |
(let ((item-end (org-list-get-item-end item struct))) |
|
2095 |
(outline-flag-region item item-end nil))))) |
|
2096 |
|
|
2097 |
(defun org-list-item-body-column (item) |
|
2098 |
"Return column at which body of ITEM should start." |
|
2099 |
(save-excursion |
|
2100 |
(goto-char item) |
|
2101 |
(if (save-excursion |
|
2102 |
(end-of-line) |
|
2103 |
(re-search-backward |
|
2104 |
"[ \t]::\\([ \t]\\|$\\)" (line-beginning-position) t)) |
|
2105 |
;; Descriptive list item. Body starts after item's tag, if |
|
2106 |
;; possible. |
|
2107 |
(let ((start (1+ (- (match-beginning 1) (line-beginning-position)))) |
|
2108 |
(ind (org-get-indentation))) |
|
2109 |
(if (> start (+ ind org-list-description-max-indent)) |
|
2110 |
(+ ind 5) |
|
2111 |
start)) |
|
2112 |
;; Regular item. Body starts after bullet. |
|
2113 |
(looking-at "[ \t]*\\(\\S-+\\)") |
|
2114 |
(+ (progn (goto-char (match-end 1)) (current-column)) |
|
2115 |
(if (and org-list-two-spaces-after-bullet-regexp |
|
2116 |
(string-match-p org-list-two-spaces-after-bullet-regexp |
|
2117 |
(match-string 1))) |
|
2118 |
2 |
|
2119 |
1))))) |
|
2120 |
|
|
2121 |
|
|
2122 |
|
|
2123 |
;;; Interactive functions |
|
2124 |
|
|
2125 |
(defalias 'org-list-get-item-begin 'org-in-item-p) |
|
2126 |
|
|
2127 |
(defun org-beginning-of-item () |
|
2128 |
"Go to the beginning of the current item. |
|
2129 |
Throw an error when not in a list." |
|
2130 |
(interactive) |
|
2131 |
(let ((begin (org-in-item-p))) |
|
2132 |
(if begin (goto-char begin) (error "Not in an item")))) |
|
2133 |
|
|
2134 |
(defun org-beginning-of-item-list () |
|
2135 |
"Go to the beginning item of the current list or sublist. |
|
2136 |
Throw an error when not in a list." |
|
2137 |
(interactive) |
|
2138 |
(let ((begin (org-in-item-p))) |
|
2139 |
(if (not begin) |
|
2140 |
(error "Not in an item") |
|
2141 |
(goto-char begin) |
|
2142 |
(let* ((struct (org-list-struct)) |
|
2143 |
(prevs (org-list-prevs-alist struct))) |
|
2144 |
(goto-char (org-list-get-list-begin begin struct prevs)))))) |
|
2145 |
|
|
2146 |
(defun org-end-of-item-list () |
|
2147 |
"Go to the end of the current list or sublist. |
|
2148 |
Throw an error when not in a list." |
|
2149 |
(interactive) |
|
2150 |
(let ((begin (org-in-item-p))) |
|
2151 |
(if (not begin) |
|
2152 |
(error "Not in an item") |
|
2153 |
(goto-char begin) |
|
2154 |
(let* ((struct (org-list-struct)) |
|
2155 |
(prevs (org-list-prevs-alist struct))) |
|
2156 |
(goto-char (org-list-get-list-end begin struct prevs)))))) |
|
2157 |
|
|
2158 |
(defun org-end-of-item () |
|
2159 |
"Go to the end of the current item. |
|
2160 |
Throw an error when not in a list." |
|
2161 |
(interactive) |
|
2162 |
(let ((begin (org-in-item-p))) |
|
2163 |
(if (not begin) |
|
2164 |
(error "Not in an item") |
|
2165 |
(goto-char begin) |
|
2166 |
(let ((struct (org-list-struct))) |
|
2167 |
(goto-char (org-list-get-item-end begin struct)))))) |
|
2168 |
|
|
2169 |
(defun org-previous-item () |
|
2170 |
"Move to the beginning of the previous item. |
|
2171 |
Throw an error when not in a list. Also throw an error when at |
|
2172 |
first item, unless `org-list-use-circular-motion' is non-nil." |
|
2173 |
(interactive) |
|
2174 |
(let ((item (org-in-item-p))) |
|
2175 |
(if (not item) |
|
2176 |
(error "Not in an item") |
|
2177 |
(goto-char item) |
|
2178 |
(let* ((struct (org-list-struct)) |
|
2179 |
(prevs (org-list-prevs-alist struct)) |
|
2180 |
(prevp (org-list-get-prev-item item struct prevs))) |
|
2181 |
(cond |
|
2182 |
(prevp (goto-char prevp)) |
|
2183 |
(org-list-use-circular-motion |
|
2184 |
(goto-char (org-list-get-last-item item struct prevs))) |
|
2185 |
(t (error "On first item"))))))) |
|
2186 |
|
|
2187 |
(defun org-next-item () |
|
2188 |
"Move to the beginning of the next item. |
|
2189 |
Throw an error when not in a list. Also throw an error when at |
|
2190 |
last item, unless `org-list-use-circular-motion' is non-nil." |
|
2191 |
(interactive) |
|
2192 |
(let ((item (org-in-item-p))) |
|
2193 |
(if (not item) |
|
2194 |
(error "Not in an item") |
|
2195 |
(goto-char item) |
|
2196 |
(let* ((struct (org-list-struct)) |
|
2197 |
(prevs (org-list-prevs-alist struct)) |
|
2198 |
(prevp (org-list-get-next-item item struct prevs))) |
|
2199 |
(cond |
|
2200 |
(prevp (goto-char prevp)) |
|
2201 |
(org-list-use-circular-motion |
|
2202 |
(goto-char (org-list-get-first-item item struct prevs))) |
|
2203 |
(t (error "On last item"))))))) |
|
2204 |
|
|
2205 |
(defun org-move-item-down () |
|
2206 |
"Move the item at point down, i.e. swap with following item. |
|
2207 |
Sub-items (items with larger indentation) are considered part of |
|
2208 |
the item, so this really moves item trees." |
|
2209 |
(interactive) |
|
2210 |
(unless (org-at-item-p) (error "Not at an item")) |
|
2211 |
(let* ((col (current-column)) |
|
2212 |
(item (point-at-bol)) |
|
2213 |
(struct (org-list-struct)) |
|
2214 |
(prevs (org-list-prevs-alist struct)) |
|
2215 |
(next-item (org-list-get-next-item (point-at-bol) struct prevs))) |
|
2216 |
(unless (or next-item org-list-use-circular-motion) |
|
2217 |
(user-error "Cannot move this item further down")) |
|
2218 |
(if (not next-item) |
|
2219 |
(setq struct (org-list-send-item item 'begin struct)) |
|
2220 |
(setq struct (org-list-swap-items item next-item struct)) |
|
2221 |
(goto-char |
|
2222 |
(org-list-get-next-item item struct (org-list-prevs-alist struct)))) |
|
2223 |
(org-list-write-struct struct (org-list-parents-alist struct)) |
|
2224 |
(org-move-to-column col))) |
|
2225 |
|
|
2226 |
(defun org-move-item-up () |
|
2227 |
"Move the item at point up, i.e. swap with previous item. |
|
2228 |
Sub-items (items with larger indentation) are considered part of |
|
2229 |
the item, so this really moves item trees." |
|
2230 |
(interactive) |
|
2231 |
(unless (org-at-item-p) (error "Not at an item")) |
|
2232 |
(let* ((col (current-column)) |
|
2233 |
(item (point-at-bol)) |
|
2234 |
(struct (org-list-struct)) |
|
2235 |
(prevs (org-list-prevs-alist struct)) |
|
2236 |
(prev-item (org-list-get-prev-item (point-at-bol) struct prevs))) |
|
2237 |
(unless (or prev-item org-list-use-circular-motion) |
|
2238 |
(user-error "Cannot move this item further up")) |
|
2239 |
(if (not prev-item) |
|
2240 |
(setq struct (org-list-send-item item 'end struct)) |
|
2241 |
(setq struct (org-list-swap-items prev-item item struct))) |
|
2242 |
(org-list-write-struct struct (org-list-parents-alist struct)) |
|
2243 |
(org-move-to-column col))) |
|
2244 |
|
|
2245 |
(defun org-insert-item (&optional checkbox) |
|
2246 |
"Insert a new item at the current level. |
|
2247 |
If cursor is before first character after bullet of the item, the |
|
2248 |
new item will be created before the current one. |
|
2249 |
|
|
2250 |
If CHECKBOX is non-nil, add a checkbox next to the bullet. |
|
2251 |
|
|
2252 |
Return t when things worked, nil when we are not in an item, or |
|
2253 |
item is invisible." |
|
2254 |
(interactive "P") |
|
2255 |
(let ((itemp (org-in-item-p)) |
|
2256 |
(pos (point))) |
|
2257 |
;; If cursor isn't is a list or if list is invisible, return nil. |
|
2258 |
(unless (or (not itemp) |
|
2259 |
(save-excursion |
|
2260 |
(goto-char itemp) |
|
2261 |
(org-invisible-p))) |
|
2262 |
(if (save-excursion |
|
2263 |
(goto-char itemp) |
|
2264 |
(org-at-item-timer-p)) |
|
2265 |
;; Timer list: delegate to `org-timer-item'. |
|
2266 |
(progn (org-timer-item) t) |
|
2267 |
(let* ((struct (save-excursion (goto-char itemp) |
|
2268 |
(org-list-struct))) |
|
2269 |
(prevs (org-list-prevs-alist struct)) |
|
2270 |
;; If we're in a description list, ask for the new term. |
|
2271 |
(desc (when (eq (org-list-get-list-type itemp struct prevs) |
|
2272 |
'descriptive) |
|
2273 |
" :: "))) |
|
2274 |
(setq struct (org-list-insert-item pos struct prevs checkbox desc)) |
|
2275 |
(org-list-write-struct struct (org-list-parents-alist struct)) |
|
2276 |
(when checkbox (org-update-checkbox-count-maybe)) |
|
2277 |
(looking-at org-list-full-item-re) |
|
2278 |
(goto-char (if (and (match-beginning 4) |
|
2279 |
(save-match-data |
|
2280 |
(string-match "[.)]" (match-string 1)))) |
|
2281 |
(match-beginning 4) |
|
2282 |
(match-end 0))) |
|
2283 |
(if desc (backward-char 1)) |
|
2284 |
t))))) |
|
2285 |
|
|
2286 |
(defun org-list-repair () |
|
2287 |
"Fix indentation, bullets and checkboxes in the list at point." |
|
2288 |
(interactive) |
|
2289 |
(unless (org-at-item-p) (error "This is not a list")) |
|
2290 |
(let* ((struct (org-list-struct)) |
|
2291 |
(parents (org-list-parents-alist struct))) |
|
2292 |
(org-list-write-struct struct parents))) |
|
2293 |
|
|
2294 |
(defun org-cycle-list-bullet (&optional which) |
|
2295 |
"Cycle through the different itemize/enumerate bullets. |
|
2296 |
This cycle the entire list level through the sequence: |
|
2297 |
|
|
2298 |
`-' -> `+' -> `*' -> `1.' -> `1)' |
|
2299 |
|
|
2300 |
If WHICH is a valid string, use that as the new bullet. If WHICH |
|
2301 |
is an integer, 0 means `-', 1 means `+' etc. If WHICH is |
|
2302 |
`previous', cycle backwards." |
|
2303 |
(interactive "P") |
|
2304 |
(unless (org-at-item-p) (error "Not at an item")) |
|
2305 |
(save-excursion |
|
2306 |
(beginning-of-line) |
|
2307 |
(let* ((struct (org-list-struct)) |
|
2308 |
(parents (org-list-parents-alist struct)) |
|
2309 |
(prevs (org-list-prevs-alist struct)) |
|
2310 |
(list-beg (org-list-get-first-item (point) struct prevs)) |
|
2311 |
(bullet (org-list-get-bullet list-beg struct)) |
|
2312 |
(alpha-p (org-list-use-alpha-bul-p list-beg struct prevs)) |
|
2313 |
(case-fold-search nil) |
|
2314 |
(current (cond |
|
2315 |
((string-match "[a-z]\\." bullet) "a.") |
|
2316 |
((string-match "[a-z])" bullet) "a)") |
|
2317 |
((string-match "[A-Z]\\." bullet) "A.") |
|
2318 |
((string-match "[A-Z])" bullet) "A)") |
|
2319 |
((string-match "\\." bullet) "1.") |
|
2320 |
((string-match ")" bullet) "1)") |
|
2321 |
(t (org-trim bullet)))) |
|
2322 |
;; Compute list of possible bullets, depending on context. |
|
2323 |
(bullet-list |
|
2324 |
(append '("-" "+" ) |
|
2325 |
;; *-bullets are not allowed at column 0. |
|
2326 |
(unless (looking-at "\\S-") '("*")) |
|
2327 |
;; Description items cannot be numbered. |
|
2328 |
(unless (or (eq org-plain-list-ordered-item-terminator ?\)) |
|
2329 |
(org-at-item-description-p)) |
|
2330 |
'("1.")) |
|
2331 |
(unless (or (eq org-plain-list-ordered-item-terminator ?.) |
|
2332 |
(org-at-item-description-p)) |
|
2333 |
'("1)")) |
|
2334 |
(unless (or (not alpha-p) |
|
2335 |
(eq org-plain-list-ordered-item-terminator ?\)) |
|
2336 |
(org-at-item-description-p)) |
|
2337 |
'("a." "A.")) |
|
2338 |
(unless (or (not alpha-p) |
|
2339 |
(eq org-plain-list-ordered-item-terminator ?.) |
|
2340 |
(org-at-item-description-p)) |
|
2341 |
'("a)" "A)")))) |
|
2342 |
(len (length bullet-list)) |
|
2343 |
(item-index (- len (length (member current bullet-list)))) |
|
2344 |
(get-value (lambda (index) (nth (mod index len) bullet-list))) |
|
2345 |
(new (cond |
|
2346 |
((member which bullet-list) which) |
|
2347 |
((numberp which) (funcall get-value which)) |
|
2348 |
((eq 'previous which) (funcall get-value (1- item-index))) |
|
2349 |
(t (funcall get-value (1+ item-index)))))) |
|
2350 |
;; Use a short variation of `org-list-write-struct' as there's |
|
2351 |
;; no need to go through all the steps. |
|
2352 |
(let ((old-struct (copy-tree struct))) |
|
2353 |
(org-list-set-bullet list-beg struct (org-list-bullet-string new)) |
|
2354 |
(org-list-struct-fix-bul struct prevs) |
|
2355 |
(org-list-struct-fix-ind struct parents) |
|
2356 |
(org-list-struct-apply-struct struct old-struct))))) |
|
2357 |
|
|
2358 |
(defun org-toggle-checkbox (&optional toggle-presence) |
|
2359 |
"Toggle the checkbox in the current line. |
|
2360 |
|
|
2361 |
With prefix argument TOGGLE-PRESENCE, add or remove checkboxes. |
|
2362 |
With a double prefix argument, set the checkbox to \"[-]\". |
|
2363 |
|
|
2364 |
When there is an active region, toggle status or presence of the |
|
2365 |
first checkbox there, and make every item inside have the same |
|
2366 |
status or presence, respectively. |
|
2367 |
|
|
2368 |
If point is on a headline, apply this to all checkbox items in |
|
2369 |
the text below the heading, taking as reference the first item in |
|
2370 |
subtree, ignoring planning line and any drawer following it." |
|
2371 |
(interactive "P") |
|
2372 |
(save-excursion |
|
2373 |
(let* (singlep |
|
2374 |
block-item |
|
2375 |
lim-up |
|
2376 |
lim-down |
|
2377 |
(orderedp (org-entry-get nil "ORDERED")) |
|
2378 |
(_bounds |
|
2379 |
;; In a region, start at first item in region. |
|
2380 |
(cond |
|
2381 |
((org-region-active-p) |
|
2382 |
(let ((limit (region-end))) |
|
2383 |
(goto-char (region-beginning)) |
|
2384 |
(if (org-list-search-forward (org-item-beginning-re) limit t) |
|
2385 |
(setq lim-up (point-at-bol)) |
|
2386 |
(error "No item in region")) |
|
2387 |
(setq lim-down (copy-marker limit)))) |
|
2388 |
((org-at-heading-p) |
|
2389 |
;; On a heading, start at first item after drawers and |
|
2390 |
;; time-stamps (scheduled, etc.). |
|
2391 |
(let ((limit (save-excursion (outline-next-heading) (point)))) |
|
2392 |
(org-end-of-meta-data t) |
|
2393 |
(if (org-list-search-forward (org-item-beginning-re) limit t) |
|
2394 |
(setq lim-up (point-at-bol)) |
|
2395 |
(error "No item in subtree")) |
|
2396 |
(setq lim-down (copy-marker limit)))) |
|
2397 |
;; Just one item: set SINGLEP flag. |
|
2398 |
((org-at-item-p) |
|
2399 |
(setq singlep t) |
|
2400 |
(setq lim-up (point-at-bol) |
|
2401 |
lim-down (copy-marker (point-at-eol)))) |
|
2402 |
(t (error "Not at an item or heading, and no active region")))) |
|
2403 |
;; Determine the checkbox going to be applied to all items |
|
2404 |
;; within bounds. |
|
2405 |
(ref-checkbox |
|
2406 |
(progn |
|
2407 |
(goto-char lim-up) |
|
2408 |
(let ((cbox (and (org-at-item-checkbox-p) (match-string 1)))) |
|
2409 |
(cond |
|
2410 |
((equal toggle-presence '(16)) "[-]") |
|
2411 |
((equal toggle-presence '(4)) |
|
2412 |
(unless cbox "[ ]")) |
|
2413 |
((equal "[X]" cbox) "[ ]") |
|
2414 |
(t "[X]")))))) |
|
2415 |
;; When an item is found within bounds, grab the full list at |
|
2416 |
;; point structure, then: (1) set check-box of all its items |
|
2417 |
;; within bounds to REF-CHECKBOX, (2) fix check-boxes of the |
|
2418 |
;; whole list, (3) move point after the list. |
|
2419 |
(goto-char lim-up) |
|
2420 |
(while (and (< (point) lim-down) |
|
2421 |
(org-list-search-forward (org-item-beginning-re) |
|
2422 |
lim-down 'move)) |
|
2423 |
(let* ((struct (org-list-struct)) |
|
2424 |
(struct-copy (copy-tree struct)) |
|
2425 |
(parents (org-list-parents-alist struct)) |
|
2426 |
(prevs (org-list-prevs-alist struct)) |
|
2427 |
(bottom (copy-marker (org-list-get-bottom-point struct))) |
|
2428 |
(items-to-toggle (cl-remove-if |
|
2429 |
(lambda (e) (or (< e lim-up) (> e lim-down))) |
|
2430 |
(mapcar #'car struct)))) |
|
2431 |
(mapc (lambda (e) (org-list-set-checkbox |
|
2432 |
e struct |
|
2433 |
;; If there is no box at item, leave as-is |
|
2434 |
;; unless function was called with C-u prefix. |
|
2435 |
(let ((cur-box (org-list-get-checkbox e struct))) |
|
2436 |
(if (or cur-box (equal toggle-presence '(4))) |
|
2437 |
ref-checkbox |
|
2438 |
cur-box)))) |
|
2439 |
items-to-toggle) |
|
2440 |
(setq block-item (org-list-struct-fix-box |
|
2441 |
struct parents prevs orderedp)) |
|
2442 |
;; Report some problems due to ORDERED status of subtree. |
|
2443 |
;; If only one box was being checked, throw an error, else, |
|
2444 |
;; only signal problems. |
|
2445 |
(cond |
|
2446 |
((and singlep block-item (> lim-up block-item)) |
|
2447 |
(error |
|
2448 |
"Checkbox blocked because of unchecked box at line %d" |
|
2449 |
(org-current-line block-item))) |
|
2450 |
(block-item |
|
2451 |
(message |
|
2452 |
"Checkboxes were removed due to unchecked box at line %d" |
|
2453 |
(org-current-line block-item)))) |
|
2454 |
(goto-char bottom) |
|
2455 |
(move-marker bottom nil) |
|
2456 |
(org-list-struct-apply-struct struct struct-copy))) |
|
2457 |
(move-marker lim-down nil))) |
|
2458 |
(org-update-checkbox-count-maybe)) |
|
2459 |
|
|
2460 |
(defun org-reset-checkbox-state-subtree () |
|
2461 |
"Reset all checkboxes in an entry subtree." |
|
2462 |
(interactive "*") |
|
2463 |
(if (org-before-first-heading-p) |
|
2464 |
(error "Not inside a tree") |
|
2465 |
(save-restriction |
|
2466 |
(save-excursion |
|
2467 |
(org-narrow-to-subtree) |
|
2468 |
(org-show-subtree) |
|
2469 |
(goto-char (point-min)) |
|
2470 |
(let ((end (point-max))) |
|
2471 |
(while (< (point) end) |
|
2472 |
(when (org-at-item-checkbox-p) |
|
2473 |
(replace-match "[ ]" t t nil 1)) |
|
2474 |
(beginning-of-line 2))) |
|
2475 |
(org-update-checkbox-count-maybe 'all))))) |
|
2476 |
|
|
2477 |
(defun org-update-checkbox-count (&optional all) |
|
2478 |
"Update the checkbox statistics in the current section. |
|
2479 |
|
|
2480 |
This will find all statistic cookies like [57%] and [6/12] and |
|
2481 |
update them with the current numbers. |
|
2482 |
|
|
2483 |
With optional prefix argument ALL, do this for the whole buffer." |
|
2484 |
(interactive "P") |
|
2485 |
(org-with-wide-buffer |
|
2486 |
(let* ((cookie-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)") |
|
2487 |
(box-re "^[ \t]*\\([-+*]\\|\\([0-9]+\\|[A-Za-z]\\)[.)]\\)[ \t]+\ |
|
2488 |
\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?\\(\\[[- X]\\]\\)") |
|
2489 |
(recursivep |
|
2490 |
(or (not org-checkbox-hierarchical-statistics) |
|
2491 |
(string-match "\\<recursive\\>" |
|
2492 |
(or (org-entry-get nil "COOKIE_DATA") "")))) |
|
2493 |
(within-inlinetask (and (not all) |
|
2494 |
(featurep 'org-inlinetask) |
|
2495 |
(org-inlinetask-in-task-p))) |
|
2496 |
(end (cond (all (point-max)) |
|
2497 |
(within-inlinetask |
|
2498 |
(save-excursion (outline-next-heading) (point))) |
|
2499 |
(t (save-excursion |
|
2500 |
(org-with-limited-levels (outline-next-heading)) |
|
2501 |
(point))))) |
|
2502 |
(count-boxes |
|
2503 |
(lambda (item structs recursivep) |
|
2504 |
;; Return number of checked boxes and boxes of all types |
|
2505 |
;; in all structures in STRUCTS. If RECURSIVEP is |
|
2506 |
;; non-nil, also count boxes in sub-lists. If ITEM is |
|
2507 |
;; nil, count across the whole structure, else count only |
|
2508 |
;; across subtree whose ancestor is ITEM. |
|
2509 |
(let ((c-on 0) (c-all 0)) |
|
2510 |
(dolist (s structs (list c-on c-all)) |
|
2511 |
(let* ((pre (org-list-prevs-alist s)) |
|
2512 |
(par (org-list-parents-alist s)) |
|
2513 |
(items |
|
2514 |
(cond |
|
2515 |
((and recursivep item) (org-list-get-subtree item s)) |
|
2516 |
(recursivep (mapcar #'car s)) |
|
2517 |
(item (org-list-get-children item s par)) |
|
2518 |
(t (org-list-get-all-items |
|
2519 |
(org-list-get-top-point s) s pre)))) |
|
2520 |
(cookies (delq nil (mapcar |
|
2521 |
(lambda (e) |
|
2522 |
(org-list-get-checkbox e s)) |
|
2523 |
items)))) |
|
2524 |
(cl-incf c-all (length cookies)) |
|
2525 |
(cl-incf c-on (cl-count "[X]" cookies :test #'equal))))))) |
|
2526 |
cookies-list cache) |
|
2527 |
;; Move to start. |
|
2528 |
(cond (all (goto-char (point-min))) |
|
2529 |
(within-inlinetask (org-back-to-heading t)) |
|
2530 |
(t (org-with-limited-levels (outline-previous-heading)))) |
|
2531 |
;; Build an alist for each cookie found. The key is the position |
|
2532 |
;; at beginning of cookie and values ending position, format of |
|
2533 |
;; cookie, number of checked boxes to report and total number of |
|
2534 |
;; boxes. |
|
2535 |
(while (re-search-forward cookie-re end t) |
|
2536 |
(let ((context (save-excursion (backward-char) |
|
2537 |
(save-match-data (org-element-context))))) |
|
2538 |
(when (eq (org-element-type context) 'statistics-cookie) |
|
2539 |
(push |
|
2540 |
(append |
|
2541 |
(list (match-beginning 1) (match-end 1) (match-end 2)) |
|
2542 |
(let* ((container |
|
2543 |
(org-element-lineage |
|
2544 |
context |
|
2545 |
'(drawer center-block dynamic-block inlinetask item |
|
2546 |
quote-block special-block verse-block))) |
|
2547 |
(beg (if container |
|
2548 |
(org-element-property :contents-begin container) |
|
2549 |
(save-excursion |
|
2550 |
(org-with-limited-levels |
|
2551 |
(outline-previous-heading)) |
|
2552 |
(point))))) |
|
2553 |
(or (cdr (assq beg cache)) |
|
2554 |
(save-excursion |
|
2555 |
(goto-char beg) |
|
2556 |
(let ((end |
|
2557 |
(if container |
|
2558 |
(org-element-property :contents-end container) |
|
2559 |
(save-excursion |
|
2560 |
(org-with-limited-levels (outline-next-heading)) |
|
2561 |
(point)))) |
|
2562 |
structs) |
|
2563 |
(while (re-search-forward box-re end t) |
|
2564 |
(let ((element (org-element-at-point))) |
|
2565 |
(when (eq (org-element-type element) 'item) |
|
2566 |
(push (org-element-property :structure element) |
|
2567 |
structs) |
|
2568 |
;; Skip whole list since we have its |
|
2569 |
;; structure anyway. |
|
2570 |
(while (setq element (org-element-lineage |
|
2571 |
element '(plain-list))) |
|
2572 |
(goto-char |
|
2573 |
(min (org-element-property :end element) |
|
2574 |
end)))))) |
|
2575 |
;; Cache count for cookies applying to the same |
|
2576 |
;; area. Then return it. |
|
2577 |
(let ((count |
|
2578 |
(funcall count-boxes |
|
2579 |
(and (eq (org-element-type container) |
|
2580 |
'item) |
|
2581 |
(org-element-property |
|
2582 |
:begin container)) |
|
2583 |
structs |
|
2584 |
recursivep))) |
|
2585 |
(push (cons beg count) cache) |
|
2586 |
count)))))) |
|
2587 |
cookies-list)))) |
|
2588 |
;; Apply alist to buffer. |
|
2589 |
(dolist (cookie cookies-list) |
|
2590 |
(let* ((beg (car cookie)) |
|
2591 |
(end (nth 1 cookie)) |
|
2592 |
(percent (nth 2 cookie)) |
|
2593 |
(checked (nth 3 cookie)) |
|
2594 |
(total (nth 4 cookie))) |
|
2595 |
(goto-char beg) |
|
2596 |
(insert |
|
2597 |
(if percent (format "[%d%%]" (floor (* 100.0 checked) |
|
2598 |
(max 1 total))) |
|
2599 |
(format "[%d/%d]" checked total))) |
|
2600 |
(delete-region (point) (+ (point) (- end beg))) |
|
2601 |
(when org-auto-align-tags (org-fix-tags-on-the-fly))))))) |
|
2602 |
|
|
2603 |
(defun org-get-checkbox-statistics-face () |
|
2604 |
"Select the face for checkbox statistics. |
|
2605 |
The face will be `org-done' when all relevant boxes are checked. |
|
2606 |
Otherwise it will be `org-todo'." |
|
2607 |
(if (match-end 1) |
|
2608 |
(if (equal (match-string 1) "100%") |
|
2609 |
'org-checkbox-statistics-done |
|
2610 |
'org-checkbox-statistics-todo) |
|
2611 |
(if (and (> (match-end 2) (match-beginning 2)) |
|
2612 |
(equal (match-string 2) (match-string 3))) |
|
2613 |
'org-checkbox-statistics-done |
|
2614 |
'org-checkbox-statistics-todo))) |
|
2615 |
|
|
2616 |
(defun org-update-checkbox-count-maybe (&optional all) |
|
2617 |
"Update checkbox statistics unless turned off by user. |
|
2618 |
With an optional argument ALL, update them in the whole buffer." |
|
2619 |
(when (cdr (assq 'checkbox org-list-automatic-rules)) |
|
2620 |
(org-update-checkbox-count all)) |
|
2621 |
(run-hooks 'org-checkbox-statistics-hook)) |
|
2622 |
|
|
2623 |
(defvar org-last-indent-begin-marker (make-marker)) |
|
2624 |
(defvar org-last-indent-end-marker (make-marker)) |
|
2625 |
(defun org-list-indent-item-generic (arg no-subtree struct) |
|
2626 |
"Indent a local list item including its children. |
|
2627 |
When number ARG is a negative, item will be outdented, otherwise |
|
2628 |
it will be indented. |
|
2629 |
|
|
2630 |
If a region is active, all items inside will be moved. |
|
2631 |
|
|
2632 |
If NO-SUBTREE is non-nil, only indent the item itself, not its |
|
2633 |
children. |
|
2634 |
|
|
2635 |
STRUCT is the list structure. |
|
2636 |
|
|
2637 |
Return t if successful." |
|
2638 |
(save-excursion |
|
2639 |
(let* ((regionp (org-region-active-p)) |
|
2640 |
(rbeg (and regionp (region-beginning))) |
|
2641 |
(rend (and regionp (region-end))) |
|
2642 |
(top (org-list-get-top-point struct)) |
|
2643 |
(parents (org-list-parents-alist struct)) |
|
2644 |
(prevs (org-list-prevs-alist struct)) |
|
2645 |
;; Are we going to move the whole list? |
|
2646 |
(specialp |
|
2647 |
(and (not regionp) |
|
2648 |
(= top (point-at-bol)) |
|
2649 |
(cdr (assq 'indent org-list-automatic-rules)) |
|
2650 |
(if no-subtree |
|
2651 |
(user-error |
|
2652 |
"At first item: use S-M-<left/right> to move the whole list") |
|
2653 |
t)))) |
|
2654 |
;; Determine begin and end points of zone to indent. If moving |
|
2655 |
;; more than one item, save them for subsequent moves. |
|
2656 |
(unless (and (memq last-command '(org-shiftmetaright org-shiftmetaleft)) |
|
2657 |
(memq this-command '(org-shiftmetaright org-shiftmetaleft))) |
|
2658 |
(if regionp |
|
2659 |
(progn |
|
2660 |
(set-marker org-last-indent-begin-marker rbeg) |
|
2661 |
(set-marker org-last-indent-end-marker rend)) |
|
2662 |
(set-marker org-last-indent-begin-marker (point-at-bol)) |
|
2663 |
(set-marker org-last-indent-end-marker |
|
2664 |
(cond |
|
2665 |
(specialp (org-list-get-bottom-point struct)) |
|
2666 |
(no-subtree (1+ (point-at-bol))) |
|
2667 |
(t (org-list-get-item-end (point-at-bol) struct)))))) |
|
2668 |
(let* ((beg (marker-position org-last-indent-begin-marker)) |
|
2669 |
(end (marker-position org-last-indent-end-marker))) |
|
2670 |
(cond |
|
2671 |
;; Special case: moving top-item with indent rule. |
|
2672 |
(specialp |
|
2673 |
(let* ((level-skip (org-level-increment)) |
|
2674 |
(offset (if (< arg 0) (- level-skip) level-skip)) |
|
2675 |
(top-ind (org-list-get-ind beg struct)) |
|
2676 |
(old-struct (copy-tree struct))) |
|
2677 |
(if (< (+ top-ind offset) 0) |
|
2678 |
(error "Cannot outdent beyond margin") |
|
2679 |
;; Change bullet if necessary. |
|
2680 |
(when (and (= (+ top-ind offset) 0) |
|
2681 |
(string-match "*" |
|
2682 |
(org-list-get-bullet beg struct))) |
|
2683 |
(org-list-set-bullet beg struct |
|
2684 |
(org-list-bullet-string "-"))) |
|
2685 |
;; Shift every item by OFFSET and fix bullets. Then |
|
2686 |
;; apply changes to buffer. |
|
2687 |
(mapc (lambda (e) |
|
2688 |
(let ((ind (org-list-get-ind (car e) struct))) |
|
2689 |
(org-list-set-ind (car e) struct (+ ind offset)))) |
|
2690 |
struct) |
|
2691 |
(org-list-struct-fix-bul struct prevs) |
|
2692 |
(org-list-struct-apply-struct struct old-struct)))) |
|
2693 |
;; Forbidden move: |
|
2694 |
((and (< arg 0) |
|
2695 |
;; If only one item is moved, it mustn't have a child. |
|
2696 |
(or (and no-subtree |
|
2697 |
(not regionp) |
|
2698 |
(org-list-has-child-p beg struct)) |
|
2699 |
;; If a subtree or region is moved, the last item |
|
2700 |
;; of the subtree mustn't have a child. |
|
2701 |
(let ((last-item (caar |
|
2702 |
(reverse |
|
2703 |
(cl-remove-if |
|
2704 |
(lambda (e) (>= (car e) end)) |
|
2705 |
struct))))) |
|
2706 |
(org-list-has-child-p last-item struct)))) |
|
2707 |
(error "Cannot outdent an item without its children")) |
|
2708 |
;; Normal shifting |
|
2709 |
(t |
|
2710 |
(let* ((new-parents |
|
2711 |
(if (< arg 0) |
|
2712 |
(org-list-struct-outdent beg end struct parents) |
|
2713 |
(org-list-struct-indent beg end struct parents prevs)))) |
|
2714 |
(org-list-write-struct struct new-parents)) |
|
2715 |
(org-update-checkbox-count-maybe)))))) |
|
2716 |
t) |
|
2717 |
|
|
2718 |
(defun org-outdent-item () |
|
2719 |
"Outdent a local list item, but not its children. |
|
2720 |
If a region is active, all items inside will be moved." |
|
2721 |
(interactive) |
|
2722 |
(let ((regionp (org-region-active-p))) |
|
2723 |
(cond |
|
2724 |
((or (org-at-item-p) |
|
2725 |
(and regionp |
|
2726 |
(save-excursion (goto-char (region-beginning)) |
|
2727 |
(org-at-item-p)))) |
|
2728 |
(let ((struct (if (not regionp) (org-list-struct) |
|
2729 |
(save-excursion (goto-char (region-beginning)) |
|
2730 |
(org-list-struct))))) |
|
2731 |
(org-list-indent-item-generic -1 t struct))) |
|
2732 |
(regionp (error "Region not starting at an item")) |
|
2733 |
(t (error "Not at an item"))))) |
|
2734 |
|
|
2735 |
(defun org-indent-item () |
|
2736 |
"Indent a local list item, but not its children. |
|
2737 |
If a region is active, all items inside will be moved." |
|
2738 |
(interactive) |
|
2739 |
(let ((regionp (org-region-active-p))) |
|
2740 |
(cond |
|
2741 |
((or (org-at-item-p) |
|
2742 |
(and regionp |
|
2743 |
(save-excursion (goto-char (region-beginning)) |
|
2744 |
(org-at-item-p)))) |
|
2745 |
(let ((struct (if (not regionp) (org-list-struct) |
|
2746 |
(save-excursion (goto-char (region-beginning)) |
|
2747 |
(org-list-struct))))) |
|
2748 |
(org-list-indent-item-generic 1 t struct))) |
|
2749 |
(regionp (error "Region not starting at an item")) |
|
2750 |
(t (error "Not at an item"))))) |
|
2751 |
|
|
2752 |
(defun org-outdent-item-tree () |
|
2753 |
"Outdent a local list item including its children. |
|
2754 |
If a region is active, all items inside will be moved." |
|
2755 |
(interactive) |
|
2756 |
(let ((regionp (org-region-active-p))) |
|
2757 |
(cond |
|
2758 |
((or (org-at-item-p) |
|
2759 |
(and regionp |
|
2760 |
(save-excursion (goto-char (region-beginning)) |
|
2761 |
(org-at-item-p)))) |
|
2762 |
(let ((struct (if (not regionp) (org-list-struct) |
|
2763 |
(save-excursion (goto-char (region-beginning)) |
|
2764 |
(org-list-struct))))) |
|
2765 |
(org-list-indent-item-generic -1 nil struct))) |
|
2766 |
(regionp (error "Region not starting at an item")) |
|
2767 |
(t (error "Not at an item"))))) |
|
2768 |
|
|
2769 |
(defun org-indent-item-tree () |
|
2770 |
"Indent a local list item including its children. |
|
2771 |
If a region is active, all items inside will be moved." |
|
2772 |
(interactive) |
|
2773 |
(let ((regionp (org-region-active-p))) |
|
2774 |
(cond |
|
2775 |
((or (org-at-item-p) |
|
2776 |
(and regionp |
|
2777 |
(save-excursion (goto-char (region-beginning)) |
|
2778 |
(org-at-item-p)))) |
|
2779 |
(let ((struct (if (not regionp) (org-list-struct) |
|
2780 |
(save-excursion (goto-char (region-beginning)) |
|
2781 |
(org-list-struct))))) |
|
2782 |
(org-list-indent-item-generic 1 nil struct))) |
|
2783 |
(regionp (error "Region not starting at an item")) |
|
2784 |
(t (error "Not at an item"))))) |
|
2785 |
|
|
2786 |
(defvar org-tab-ind-state) |
|
2787 |
(defvar org-adapt-indentation) |
|
2788 |
(defun org-cycle-item-indentation () |
|
2789 |
"Cycle levels of indentation of an empty item. |
|
2790 |
The first run indents the item, if applicable. Subsequent runs |
|
2791 |
outdent it at meaningful levels in the list. When done, item is |
|
2792 |
put back at its original position with its original bullet. |
|
2793 |
|
|
2794 |
Return t at each successful move." |
|
2795 |
(when (org-at-item-p) |
|
2796 |
(let* ((org-adapt-indentation nil) |
|
2797 |
(struct (org-list-struct)) |
|
2798 |
(ind (org-list-get-ind (point-at-bol) struct)) |
|
2799 |
(bullet (org-trim (buffer-substring (point-at-bol) (point-at-eol))))) |
|
2800 |
;; Accept empty items or if cycle has already started. |
|
2801 |
(when (or (eq last-command 'org-cycle-item-indentation) |
|
2802 |
(and (save-excursion |
|
2803 |
(beginning-of-line) |
|
2804 |
(looking-at org-list-full-item-re)) |
|
2805 |
(>= (match-end 0) (save-excursion |
|
2806 |
(goto-char (org-list-get-item-end |
|
2807 |
(point-at-bol) struct)) |
|
2808 |
(skip-chars-backward " \r\t\n") |
|
2809 |
(point))))) |
|
2810 |
(setq this-command 'org-cycle-item-indentation) |
|
2811 |
;; When in the middle of the cycle, try to outdent first. If |
|
2812 |
;; it fails, and point is still at initial position, indent. |
|
2813 |
;; Else, re-create it at its original position. |
|
2814 |
(if (eq last-command 'org-cycle-item-indentation) |
|
2815 |
(cond |
|
2816 |
((ignore-errors (org-list-indent-item-generic -1 t struct))) |
|
2817 |
((and (= ind (car org-tab-ind-state)) |
|
2818 |
(ignore-errors (org-list-indent-item-generic 1 t struct)))) |
|
2819 |
(t (delete-region (point-at-bol) (point-at-eol)) |
|
2820 |
(indent-to-column (car org-tab-ind-state)) |
|
2821 |
(insert (cdr org-tab-ind-state) " ") |
|
2822 |
;; Break cycle |
|
2823 |
(setq this-command 'identity))) |
|
2824 |
;; If a cycle is starting, remember indentation and bullet, |
|
2825 |
;; then try to indent. If it fails, try to outdent. |
|
2826 |
(setq org-tab-ind-state (cons ind bullet)) |
|
2827 |
(cond |
|
2828 |
((ignore-errors (org-list-indent-item-generic 1 t struct))) |
|
2829 |
((ignore-errors (org-list-indent-item-generic -1 t struct))) |
|
2830 |
(t (user-error "Cannot move item")))) |
|
2831 |
t)))) |
|
2832 |
|
|
2833 |
(defun org-sort-list |
|
2834 |
(&optional with-case sorting-type getkey-func compare-func interactive?) |
|
2835 |
"Sort list items. |
|
2836 |
The cursor may be at any item of the list that should be sorted. |
|
2837 |
Sublists are not sorted. Checkboxes, if any, are ignored. |
|
2838 |
|
|
2839 |
Sorting can be alphabetically, numerically, by date/time as given |
|
2840 |
by a time stamp, by a property or by priority. |
|
2841 |
|
|
2842 |
Comparing entries ignores case by default. However, with an |
|
2843 |
optional argument WITH-CASE, the sorting considers case as well. |
|
2844 |
|
|
2845 |
The command prompts for the sorting type unless it has been given |
|
2846 |
to the function through the SORTING-TYPE argument, which needs to |
|
2847 |
be a character, \(?n ?N ?a ?A ?t ?T ?f ?F ?x ?X). Here is the |
|
2848 |
detailed meaning of each character: |
|
2849 |
|
|
2850 |
n Numerically, by converting the beginning of the item to a number. |
|
2851 |
a Alphabetically. Only the first line of item is checked. |
|
2852 |
t By date/time, either the first active time stamp in the entry, if |
|
2853 |
any, or by the first inactive one. In a timer list, sort the timers. |
|
2854 |
x By \"checked\" status of a check list. |
|
2855 |
|
|
2856 |
Capital letters will reverse the sort order. |
|
2857 |
|
|
2858 |
If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies |
|
2859 |
a function to be called with point at the beginning of the |
|
2860 |
record. It must return a value that is compatible with COMPARE-FUNC, |
|
2861 |
the function used to compare entries. |
|
2862 |
|
|
2863 |
Sorting is done against the visible part of the headlines, it |
|
2864 |
ignores hidden links. |
|
2865 |
|
|
2866 |
A non-nil value for INTERACTIVE? is used to signal that this |
|
2867 |
function is being called interactively." |
|
2868 |
(interactive (list current-prefix-arg nil nil nil t)) |
|
2869 |
(let* ((case-func (if with-case 'identity 'downcase)) |
|
2870 |
(struct (org-list-struct)) |
|
2871 |
(prevs (org-list-prevs-alist struct)) |
|
2872 |
(start (org-list-get-list-begin (point-at-bol) struct prevs)) |
|
2873 |
(end (org-list-get-list-end (point-at-bol) struct prevs)) |
|
2874 |
(sorting-type |
|
2875 |
(or sorting-type |
|
2876 |
(progn |
|
2877 |
(message |
|
2878 |
"Sort plain list: [a]lpha [n]umeric [t]ime [f]unc [x]checked A/N/T/F/X means reversed:") |
|
2879 |
(read-char-exclusive)))) |
|
2880 |
(dcst (downcase sorting-type)) |
|
2881 |
(getkey-func |
|
2882 |
(and (= dcst ?f) |
|
2883 |
(or getkey-func |
|
2884 |
(and interactive? |
|
2885 |
(org-read-function "Function for extracting keys: ")) |
|
2886 |
(error "Missing key extractor")))) |
|
2887 |
(sort-func |
|
2888 |
(cond |
|
2889 |
((= dcst ?a) #'string<) |
|
2890 |
((= dcst ?f) |
|
2891 |
(or compare-func |
|
2892 |
(and interactive? |
|
2893 |
(org-read-function |
|
2894 |
(concat "Function for comparing keys " |
|
2895 |
"(empty for default `sort-subr' predicate): ") |
|
2896 |
'allow-empty)))) |
|
2897 |
((= dcst ?t) #'<) |
|
2898 |
((= dcst ?x) #'string<)))) |
|
2899 |
(message "Sorting items...") |
|
2900 |
(save-restriction |
|
2901 |
(narrow-to-region start end) |
|
2902 |
(goto-char (point-min)) |
|
2903 |
(let* ((case-fold-search nil) |
|
2904 |
(now (current-time)) |
|
2905 |
(next-record (lambda () |
|
2906 |
(skip-chars-forward " \r\t\n") |
|
2907 |
(or (eobp) (beginning-of-line)))) |
|
2908 |
(end-record (lambda () |
|
2909 |
(goto-char (org-list-get-item-end-before-blank |
|
2910 |
(point) struct)))) |
|
2911 |
(value-to-sort |
|
2912 |
(lambda () |
|
2913 |
(when (looking-at "[ \t]*[-+*0-9.)]+\\([ \t]+\\[[- X]\\]\\)?[ \t]+") |
|
2914 |
(cond |
|
2915 |
((= dcst ?n) |
|
2916 |
(string-to-number |
|
2917 |
(org-sort-remove-invisible |
|
2918 |
(buffer-substring (match-end 0) (point-at-eol))))) |
|
2919 |
((= dcst ?a) |
|
2920 |
(funcall case-func |
|
2921 |
(org-sort-remove-invisible |
|
2922 |
(buffer-substring |
|
2923 |
(match-end 0) (point-at-eol))))) |
|
2924 |
((= dcst ?t) |
|
2925 |
(cond |
|
2926 |
;; If it is a timer list, convert timer to seconds |
|
2927 |
((org-at-item-timer-p) |
|
2928 |
(org-timer-hms-to-secs (match-string 1))) |
|
2929 |
((or (save-excursion |
|
2930 |
(re-search-forward org-ts-regexp (point-at-eol) t)) |
|
2931 |
(save-excursion (re-search-forward org-ts-regexp-both |
|
2932 |
(point-at-eol) t))) |
|
2933 |
(org-time-string-to-seconds (match-string 0))) |
|
2934 |
(t (float-time now)))) |
|
2935 |
((= dcst ?x) (or (and (stringp (match-string 1)) |
|
2936 |
(match-string 1)) |
|
2937 |
"")) |
|
2938 |
((= dcst ?f) |
|
2939 |
(if getkey-func |
|
2940 |
(let ((value (funcall getkey-func))) |
|
2941 |
(if (stringp value) |
|
2942 |
(funcall case-func value) |
|
2943 |
value)) |
|
2944 |
(error "Invalid key function `%s'" getkey-func))) |
|
2945 |
(t (error "Invalid sorting type `%c'" sorting-type))))))) |
|
2946 |
(sort-subr (/= dcst sorting-type) |
|
2947 |
next-record |
|
2948 |
end-record |
|
2949 |
value-to-sort |
|
2950 |
nil |
|
2951 |
sort-func) |
|
2952 |
;; Read and fix list again, as `sort-subr' probably destroyed |
|
2953 |
;; its structure. |
|
2954 |
(org-list-repair) |
|
2955 |
(run-hooks 'org-after-sorting-entries-or-items-hook) |
|
2956 |
(message "Sorting items...done"))))) |
|
2957 |
|
|
2958 |
(defun org-toggle-item (arg) |
|
2959 |
"Convert headings or normal lines to items, items to normal lines. |
|
2960 |
If there is no active region, only the current line is considered. |
|
2961 |
|
|
2962 |
If the first non blank line in the region is a headline, convert |
|
2963 |
all headlines to items, shifting text accordingly. |
|
2964 |
|
|
2965 |
If it is an item, convert all items to normal lines. |
|
2966 |
|
|
2967 |
If it is normal text, change region into a list of items. |
|
2968 |
With a prefix argument ARG, change the region in a single item." |
|
2969 |
(interactive "P") |
|
2970 |
(let ((shift-text |
|
2971 |
(lambda (ind end) |
|
2972 |
;; Shift text in current section to IND, from point to END. |
|
2973 |
;; The function leaves point to END line. |
|
2974 |
(let ((min-i 1000) (end (copy-marker end))) |
|
2975 |
;; First determine the minimum indentation (MIN-I) of |
|
2976 |
;; the text. |
|
2977 |
(save-excursion |
|
2978 |
(catch 'exit |
|
2979 |
(while (< (point) end) |
|
2980 |
(let ((i (org-get-indentation))) |
|
2981 |
(cond |
|
2982 |
;; Skip blank lines and inline tasks. |
|
2983 |
((looking-at "^[ \t]*$")) |
|
2984 |
((looking-at org-outline-regexp-bol)) |
|
2985 |
;; We can't find less than 0 indentation. |
|
2986 |
((zerop i) (throw 'exit (setq min-i 0))) |
|
2987 |
((< i min-i) (setq min-i i)))) |
|
2988 |
(forward-line)))) |
|
2989 |
;; Then indent each line so that a line indented to |
|
2990 |
;; MIN-I becomes indented to IND. Ignore blank lines |
|
2991 |
;; and inline tasks in the process. |
|
2992 |
(let ((delta (- ind min-i))) |
|
2993 |
(while (< (point) end) |
|
2994 |
(unless (or (looking-at "^[ \t]*$") |
|
2995 |
(looking-at org-outline-regexp-bol)) |
|
2996 |
(indent-line-to (+ (org-get-indentation) delta))) |
|
2997 |
(forward-line)))))) |
|
2998 |
(skip-blanks |
|
2999 |
(lambda (pos) |
|
3000 |
;; Return beginning of first non-blank line, starting from |
|
3001 |
;; line at POS. |
|
3002 |
(save-excursion |
|
3003 |
(goto-char pos) |
|
3004 |
(skip-chars-forward " \r\t\n") |
|
3005 |
(point-at-bol)))) |
|
3006 |
beg end) |
|
3007 |
;; Determine boundaries of changes. |
|
3008 |
(if (org-region-active-p) |
|
3009 |
(setq beg (funcall skip-blanks (region-beginning)) |
|
3010 |
end (copy-marker (region-end))) |
|
3011 |
(setq beg (funcall skip-blanks (point-at-bol)) |
|
3012 |
end (copy-marker (point-at-eol)))) |
|
3013 |
;; Depending on the starting line, choose an action on the text |
|
3014 |
;; between BEG and END. |
|
3015 |
(org-with-limited-levels |
|
3016 |
(save-excursion |
|
3017 |
(goto-char beg) |
|
3018 |
(cond |
|
3019 |
;; Case 1. Start at an item: de-itemize. Note that it only |
|
3020 |
;; happens when a region is active: `org-ctrl-c-minus' |
|
3021 |
;; would call `org-cycle-list-bullet' otherwise. |
|
3022 |
((org-at-item-p) |
|
3023 |
(while (< (point) end) |
|
3024 |
(when (org-at-item-p) |
|
3025 |
(skip-chars-forward " \t") |
|
3026 |
(delete-region (point) (match-end 0))) |
|
3027 |
(forward-line))) |
|
3028 |
;; Case 2. Start at an heading: convert to items. |
|
3029 |
((org-at-heading-p) |
|
3030 |
(let* ((bul (org-list-bullet-string "-")) |
|
3031 |
(bul-len (length bul)) |
|
3032 |
;; Indentation of the first heading. It should be |
|
3033 |
;; relative to the indentation of its parent, if any. |
|
3034 |
(start-ind (save-excursion |
|
3035 |
(cond |
|
3036 |
((not org-adapt-indentation) 0) |
|
3037 |
((not (outline-previous-heading)) 0) |
|
3038 |
(t (length (match-string 0)))))) |
|
3039 |
;; Level of first heading. Further headings will be |
|
3040 |
;; compared to it to determine hierarchy in the list. |
|
3041 |
(ref-level (org-reduced-level (org-outline-level)))) |
|
3042 |
(while (< (point) end) |
|
3043 |
(let* ((level (org-reduced-level (org-outline-level))) |
|
3044 |
(delta (max 0 (- level ref-level))) |
|
3045 |
(todo-state (org-get-todo-state))) |
|
3046 |
;; If current headline is less indented than the first |
|
3047 |
;; one, set it as reference, in order to preserve |
|
3048 |
;; subtrees. |
|
3049 |
(when (< level ref-level) (setq ref-level level)) |
|
3050 |
;; Remove stars and TODO keyword. |
|
3051 |
(let ((case-fold-search nil)) (looking-at org-todo-line-regexp)) |
|
3052 |
(delete-region (point) (or (match-beginning 3) |
|
3053 |
(line-end-position))) |
|
3054 |
(insert bul) |
|
3055 |
(indent-line-to (+ start-ind (* delta bul-len))) |
|
3056 |
;; Turn TODO keyword into a check box. |
|
3057 |
(when todo-state |
|
3058 |
(let* ((struct (org-list-struct)) |
|
3059 |
(old (copy-tree struct))) |
|
3060 |
(org-list-set-checkbox |
|
3061 |
(line-beginning-position) |
|
3062 |
struct |
|
3063 |
(if (member todo-state org-done-keywords) |
|
3064 |
"[X]" |
|
3065 |
"[ ]")) |
|
3066 |
(org-list-write-struct struct |
|
3067 |
(org-list-parents-alist struct) |
|
3068 |
old))) |
|
3069 |
;; Ensure all text down to END (or SECTION-END) belongs |
|
3070 |
;; to the newly created item. |
|
3071 |
(let ((section-end (save-excursion |
|
3072 |
(or (outline-next-heading) (point))))) |
|
3073 |
(forward-line) |
|
3074 |
(funcall shift-text |
|
3075 |
(+ start-ind (* (1+ delta) bul-len)) |
|
3076 |
(min end section-end))))))) |
|
3077 |
;; Case 3. Normal line with ARG: make the first line of region |
|
3078 |
;; an item, and shift indentation of others lines to |
|
3079 |
;; set them as item's body. |
|
3080 |
(arg (let* ((bul (org-list-bullet-string "-")) |
|
3081 |
(bul-len (length bul)) |
|
3082 |
(ref-ind (org-get-indentation))) |
|
3083 |
(skip-chars-forward " \t") |
|
3084 |
(insert bul) |
|
3085 |
(forward-line) |
|
3086 |
(while (< (point) end) |
|
3087 |
;; Ensure that lines less indented than first one |
|
3088 |
;; still get included in item body. |
|
3089 |
(funcall shift-text |
|
3090 |
(+ ref-ind bul-len) |
|
3091 |
(min end (save-excursion (or (outline-next-heading) |
|
3092 |
(point))))) |
|
3093 |
(forward-line)))) |
|
3094 |
;; Case 4. Normal line without ARG: turn each non-item line |
|
3095 |
;; into an item. |
|
3096 |
(t |
|
3097 |
(while (< (point) end) |
|
3098 |
(unless (or (org-at-heading-p) (org-at-item-p)) |
|
3099 |
(when (looking-at "\\([ \t]*\\)\\(\\S-\\)") |
|
3100 |
(replace-match |
|
3101 |
(concat "\\1" (org-list-bullet-string "-") "\\2")))) |
|
3102 |
(forward-line)))))))) |
|
3103 |
|
|
3104 |
|
|
3105 |
;;; Send and receive lists |
|
3106 |
|
|
3107 |
(defun org-list-to-lisp (&optional delete) |
|
3108 |
"Parse the list at point and maybe DELETE it. |
|
3109 |
|
|
3110 |
Return a list whose car is a symbol of list type, among |
|
3111 |
`ordered', `unordered' and `descriptive'. Then, each item is |
|
3112 |
a list of strings and other sub-lists. |
|
3113 |
|
|
3114 |
For example, the following list: |
|
3115 |
|
|
3116 |
1. first item |
|
3117 |
+ sub-item one |
|
3118 |
+ [X] sub-item two |
|
3119 |
more text in first item |
|
3120 |
2. [@3] last item |
|
3121 |
|
|
3122 |
is parsed as |
|
3123 |
|
|
3124 |
(ordered |
|
3125 |
(\"first item\" |
|
3126 |
(unordered |
|
3127 |
(\"sub-item one\") |
|
3128 |
(\"[X] sub-item two\")) |
|
3129 |
\"more text in first item\") |
|
3130 |
(\"[@3] last item\")) |
|
3131 |
|
|
3132 |
Point is left at list's end." |
|
3133 |
(letrec ((struct (org-list-struct)) |
|
3134 |
(prevs (org-list-prevs-alist struct)) |
|
3135 |
(parents (org-list-parents-alist struct)) |
|
3136 |
(top (org-list-get-top-point struct)) |
|
3137 |
(bottom (org-list-get-bottom-point struct)) |
|
3138 |
(trim |
|
3139 |
(lambda (text) |
|
3140 |
;; Remove indentation and final newline from TEXT. |
|
3141 |
(org-remove-indentation |
|
3142 |
(if (string-match-p "\n\\'" text) |
|
3143 |
(substring text 0 -1) |
|
3144 |
text)))) |
|
3145 |
(parse-sublist |
|
3146 |
(lambda (e) |
|
3147 |
;; Return a list whose car is list type and cdr a list |
|
3148 |
;; of items' body. |
|
3149 |
(cons (org-list-get-list-type (car e) struct prevs) |
|
3150 |
(mapcar parse-item e)))) |
|
3151 |
(parse-item |
|
3152 |
(lambda (e) |
|
3153 |
;; Return a list containing counter of item, if any, |
|
3154 |
;; text and any sublist inside it. |
|
3155 |
(let* ((end (org-list-get-item-end e struct)) |
|
3156 |
(children (org-list-get-children e struct parents)) |
|
3157 |
(body |
|
3158 |
(save-excursion |
|
3159 |
(goto-char e) |
|
3160 |
(looking-at "[ \t]*\\S-+[ \t]*") |
|
3161 |
(list |
|
3162 |
(funcall |
|
3163 |
trim |
|
3164 |
(concat |
|
3165 |
(make-string (string-width (match-string 0)) ?\s) |
|
3166 |
(buffer-substring-no-properties |
|
3167 |
(match-end 0) (or (car children) end)))))))) |
|
3168 |
(while children |
|
3169 |
(let* ((child (car children)) |
|
3170 |
(sub (org-list-get-all-items child struct prevs)) |
|
3171 |
(last-in-sub (car (last sub)))) |
|
3172 |
(push (funcall parse-sublist sub) body) |
|
3173 |
;; Remove whole sub-list from children. |
|
3174 |
(setq children (cdr (memq last-in-sub children))) |
|
3175 |
;; There is a chunk of text belonging to the item |
|
3176 |
;; if last child doesn't end where next child |
|
3177 |
;; starts or where item ends. |
|
3178 |
(let ((sub-end (org-list-get-item-end last-in-sub struct)) |
|
3179 |
(next (or (car children) end))) |
|
3180 |
(when (/= sub-end next) |
|
3181 |
(push (funcall |
|
3182 |
trim |
|
3183 |
(buffer-substring-no-properties sub-end next)) |
|
3184 |
body))))) |
|
3185 |
(nreverse body))))) |
|
3186 |
;; Store output, take care of cursor position and deletion of |
|
3187 |
;; list, then return output. |
|
3188 |
(prog1 (funcall parse-sublist (org-list-get-all-items top struct prevs)) |
|
3189 |
(goto-char top) |
|
3190 |
(when delete |
|
3191 |
(delete-region top bottom) |
|
3192 |
(when (and (not (looking-at "[ \t]*$")) (looking-at org-list-end-re)) |
|
3193 |
(replace-match "")))))) |
|
3194 |
|
|
3195 |
(defun org-list-make-subtree () |
|
3196 |
"Convert the plain list at point into a subtree." |
|
3197 |
(interactive) |
|
3198 |
(if (not (ignore-errors (goto-char (org-in-item-p)))) |
|
3199 |
(error "Not in a list") |
|
3200 |
(let ((list (save-excursion (org-list-to-lisp t)))) |
|
3201 |
(insert (org-list-to-subtree list))))) |
|
3202 |
|
|
3203 |
(defun org-list-insert-radio-list () |
|
3204 |
"Insert a radio list template appropriate for this major mode." |
|
3205 |
(interactive) |
|
3206 |
(let* ((e (cl-assoc-if #'derived-mode-p org-list-radio-list-templates)) |
|
3207 |
(txt (nth 1 e)) |
|
3208 |
name pos) |
|
3209 |
(unless e (error "No radio list setup defined for %s" major-mode)) |
|
3210 |
(setq name (read-string "List name: ")) |
|
3211 |
(while (string-match "%n" txt) |
|
3212 |
(setq txt (replace-match name t t txt))) |
|
3213 |
(or (bolp) (insert "\n")) |
|
3214 |
(setq pos (point)) |
|
3215 |
(insert txt) |
|
3216 |
(goto-char pos))) |
|
3217 |
|
|
3218 |
(defun org-list-send-list (&optional maybe) |
|
3219 |
"Send a transformed version of this list to the receiver position. |
|
3220 |
With argument MAYBE, fail quietly if no transformation is defined |
|
3221 |
for this list." |
|
3222 |
(interactive) |
|
3223 |
(catch 'exit |
|
3224 |
(unless (org-at-item-p) (error "Not at a list item")) |
|
3225 |
(save-excursion |
|
3226 |
(let ((case-fold-search t)) |
|
3227 |
(re-search-backward "^[ \t]*#\\+ORGLST:" nil t) |
|
3228 |
(unless (looking-at |
|
3229 |
"[ \t]*#\\+ORGLST:[ \t]+SEND[ \t]+\\(\\S-+\\)[ \t]+\\([^ \t\n]+\\)") |
|
3230 |
(if maybe (throw 'exit nil) |
|
3231 |
(error "Don't know how to transform this list"))))) |
|
3232 |
(let* ((name (regexp-quote (match-string 1))) |
|
3233 |
(transform (intern (match-string 2))) |
|
3234 |
(bottom-point |
|
3235 |
(save-excursion |
|
3236 |
(re-search-forward |
|
3237 |
"\\(\\\\end{comment}\\|@end ignore\\|-->\\)" nil t) |
|
3238 |
(match-beginning 0))) |
|
3239 |
(top-point |
|
3240 |
(progn |
|
3241 |
(re-search-backward "#\\+ORGLST" nil t) |
|
3242 |
(re-search-forward (org-item-beginning-re) bottom-point t) |
|
3243 |
(match-beginning 0))) |
|
3244 |
(plain-list (save-excursion |
|
3245 |
(goto-char top-point) |
|
3246 |
(org-list-to-lisp)))) |
|
3247 |
(unless (fboundp transform) |
|
3248 |
(error "No such transformation function %s" transform)) |
|
3249 |
(let ((txt (funcall transform plain-list))) |
|
3250 |
;; Find the insertion(s) place(s). |
|
3251 |
(save-excursion |
|
3252 |
(goto-char (point-min)) |
|
3253 |
(let ((receiver-count 0) |
|
3254 |
(begin-re (format "BEGIN +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)" |
|
3255 |
name)) |
|
3256 |
(end-re (format "END +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)" |
|
3257 |
name))) |
|
3258 |
(while (re-search-forward begin-re nil t) |
|
3259 |
(cl-incf receiver-count) |
|
3260 |
(let ((beg (line-beginning-position 2))) |
|
3261 |
(unless (re-search-forward end-re nil t) |
|
3262 |
(user-error "Cannot find end of receiver location at %d" beg)) |
|
3263 |
(beginning-of-line) |
|
3264 |
(delete-region beg (point)) |
|
3265 |
(insert txt "\n"))) |
|
3266 |
(cond |
|
3267 |
((> receiver-count 1) |
|
3268 |
(message "List converted and installed at receiver locations")) |
|
3269 |
((= receiver-count 1) |
|
3270 |
(message "List converted and installed at receiver location")) |
|
3271 |
(t (user-error "No valid receiver location found"))))))))) |
|
3272 |
|
|
3273 |
(defun org-list-to-generic (list params) |
|
3274 |
"Convert a LIST parsed through `org-list-to-lisp' to a custom format. |
|
3275 |
|
|
3276 |
LIST is a list as returned by `org-list-to-lisp', which see. |
|
3277 |
PARAMS is a property list of parameters used to tweak the output |
|
3278 |
format. |
|
3279 |
|
|
3280 |
Valid parameters are: |
|
3281 |
|
|
3282 |
:backend, :raw |
|
3283 |
|
|
3284 |
Export back-end used as a basis to transcode elements of the |
|
3285 |
list, when no specific parameter applies to it. It is also |
|
3286 |
used to translate its contents. You can prevent this by |
|
3287 |
setting :raw property to a non-nil value. |
|
3288 |
|
|
3289 |
:splice |
|
3290 |
|
|
3291 |
When non-nil, only export the contents of the top most plain |
|
3292 |
list, effectively ignoring its opening and closing lines. |
|
3293 |
|
|
3294 |
:ustart, :uend |
|
3295 |
|
|
3296 |
Strings to start and end an unordered list. They can also be |
|
3297 |
set to a function returning a string or nil, which will be |
|
3298 |
called with the depth of the list, counting from 1. |
|
3299 |
|
|
3300 |
:ostart, :oend |
|
3301 |
|
|
3302 |
Strings to start and end an ordered list. They can also be set |
|
3303 |
to a function returning a string or nil, which will be called |
|
3304 |
with the depth of the list, counting from 1. |
|
3305 |
|
|
3306 |
:dstart, :dend |
|
3307 |
|
|
3308 |
Strings to start and end a descriptive list. They can also be |
|
3309 |
set to a function returning a string or nil, which will be |
|
3310 |
called with the depth of the list, counting from 1. |
|
3311 |
|
|
3312 |
:dtstart, :dtend, :ddstart, :ddend |
|
3313 |
|
|
3314 |
Strings to start and end a descriptive term. |
|
3315 |
|
|
3316 |
:istart, :iend |
|
3317 |
|
|
3318 |
Strings to start or end a list item, and to start a list item |
|
3319 |
with a counter. They can also be set to a function returning |
|
3320 |
a string or nil, which will be called with two arguments: the |
|
3321 |
type of list and the depth of the item, counting from 1. |
|
3322 |
|
|
3323 |
:icount |
|
3324 |
|
|
3325 |
Strings to start a list item with a counter. It can also be |
|
3326 |
set to a function returning a string or nil, which will be |
|
3327 |
called with three arguments: the type of list, the depth of the |
|
3328 |
item, counting from 1, and the counter. Its value, when |
|
3329 |
non-nil, has precedence over `:istart'. |
|
3330 |
|
|
3331 |
:isep |
|
3332 |
|
|
3333 |
String used to separate items. It can also be set to |
|
3334 |
a function returning a string or nil, which will be called with |
|
3335 |
two arguments: the type of list and the depth of the item, |
|
3336 |
counting from 1. It always start on a new line. |
|
3337 |
|
|
3338 |
:ifmt |
|
3339 |
|
|
3340 |
Function to be applied to the contents of every item. It is |
|
3341 |
called with two arguments: the type of list and the contents. |
|
3342 |
|
|
3343 |
:cbon, :cboff, :cbtrans |
|
3344 |
|
|
3345 |
String to insert, respectively, an un-checked check-box, |
|
3346 |
a checked check-box and a check-box in transitional state." |
|
3347 |
(require 'ox) |
|
3348 |
(let* ((backend (plist-get params :backend)) |
|
3349 |
(custom-backend |
|
3350 |
(org-export-create-backend |
|
3351 |
:parent (or backend 'org) |
|
3352 |
:transcoders |
|
3353 |
`((plain-list . ,(org-list--to-generic-plain-list params)) |
|
3354 |
(item . ,(org-list--to-generic-item params)) |
|
3355 |
(macro . (lambda (m c i) (org-element-macro-interpreter m nil)))))) |
|
3356 |
data info) |
|
3357 |
;; Write LIST back into Org syntax and parse it. |
|
3358 |
(with-temp-buffer |
|
3359 |
(let ((org-inhibit-startup t)) (org-mode)) |
|
3360 |
(letrec ((insert-list |
|
3361 |
(lambda (l) |
|
3362 |
(dolist (i (cdr l)) |
|
3363 |
(funcall insert-item i (car l))))) |
|
3364 |
(insert-item |
|
3365 |
(lambda (i type) |
|
3366 |
(let ((start (point))) |
|
3367 |
(insert (if (eq type 'ordered) "1. " "- ")) |
|
3368 |
(dolist (e i) |
|
3369 |
(if (consp e) (funcall insert-list e) |
|
3370 |
(insert e) |
|
3371 |
(insert "\n"))) |
|
3372 |
(beginning-of-line) |
|
3373 |
(save-excursion |
|
3374 |
(let ((ind (if (eq type 'ordered) 3 2))) |
|
3375 |
(while (> (point) start) |
|
3376 |
(unless (looking-at-p "[ \t]*$") |
|
3377 |
(indent-to ind)) |
|
3378 |
(forward-line -1)))))))) |
|
3379 |
(funcall insert-list list)) |
|
3380 |
(setf data |
|
3381 |
(org-element-map (org-element-parse-buffer) 'plain-list |
|
3382 |
#'identity nil t)) |
|
3383 |
(setf info (org-export-get-environment backend nil params))) |
|
3384 |
(when (and backend (symbolp backend) (not (org-export-get-backend backend))) |
|
3385 |
(user-error "Unknown :backend value")) |
|
3386 |
(unless backend (require 'ox-org)) |
|
3387 |
;; When`:raw' property has a non-nil value, turn all objects back |
|
3388 |
;; into Org syntax. |
|
3389 |
(when (and backend (plist-get params :raw)) |
|
3390 |
(org-element-map data org-element-all-objects |
|
3391 |
(lambda (object) |
|
3392 |
(org-element-set-element |
|
3393 |
object (org-element-interpret-data object))))) |
|
3394 |
;; We use a low-level mechanism to export DATA so as to skip all |
|
3395 |
;; usual pre-processing and post-processing, i.e., hooks, filters, |
|
3396 |
;; Babel code evaluation, include keywords and macro expansion, |
|
3397 |
;; and filters. |
|
3398 |
(let ((output (org-export-data-with-backend data custom-backend info))) |
|
3399 |
;; Remove final newline. |
|
3400 |
(if (org-string-nw-p output) (substring-no-properties output 0 -1) "")))) |
|
3401 |
|
|
3402 |
(defun org-list--depth (element) |
|
3403 |
"Return the level of ELEMENT within current plain list. |
|
3404 |
ELEMENT is either an item or a plain list." |
|
3405 |
(cl-count-if (lambda (ancestor) (eq (org-element-type ancestor) 'plain-list)) |
|
3406 |
(org-element-lineage element nil t))) |
|
3407 |
|
|
3408 |
(defun org-list--trailing-newlines (string) |
|
3409 |
"Return the number of trailing newlines in STRING." |
|
3410 |
(with-temp-buffer |
|
3411 |
(insert string) |
|
3412 |
(skip-chars-backward " \t\n") |
|
3413 |
(count-lines (line-beginning-position 2) (point-max)))) |
|
3414 |
|
|
3415 |
(defun org-list--generic-eval (value &rest args) |
|
3416 |
"Evaluate VALUE according to its type. |
|
3417 |
VALUE is either nil, a string or a function. In the latter case, |
|
3418 |
it is called with arguments ARGS." |
|
3419 |
(cond ((null value) nil) |
|
3420 |
((stringp value) value) |
|
3421 |
((functionp value) (apply value args)) |
|
3422 |
(t (error "Wrong value: %s" value)))) |
|
3423 |
|
|
3424 |
(defun org-list--to-generic-plain-list (params) |
|
3425 |
"Return a transcoder for `plain-list' elements. |
|
3426 |
PARAMS is a plist used to tweak the behavior of the transcoder." |
|
3427 |
(let ((ustart (plist-get params :ustart)) |
|
3428 |
(uend (plist-get params :uend)) |
|
3429 |
(ostart (plist-get params :ostart)) |
|
3430 |
(oend (plist-get params :oend)) |
|
3431 |
(dstart (plist-get params :dstart)) |
|
3432 |
(dend (plist-get params :dend)) |
|
3433 |
(splice (plist-get params :splice)) |
|
3434 |
(backend (plist-get params :backend))) |
|
3435 |
(lambda (plain-list contents info) |
|
3436 |
(let* ((type (org-element-property :type plain-list)) |
|
3437 |
(depth (org-list--depth plain-list)) |
|
3438 |
(start (and (not splice) |
|
3439 |
(org-list--generic-eval |
|
3440 |
(pcase type |
|
3441 |
(`ordered ostart) |
|
3442 |
(`unordered ustart) |
|
3443 |
(_ dstart)) |
|
3444 |
depth))) |
|
3445 |
(end (and (not splice) |
|
3446 |
(org-list--generic-eval |
|
3447 |
(pcase type |
|
3448 |
(`ordered oend) |
|
3449 |
(`unordered uend) |
|
3450 |
(_ dend)) |
|
3451 |
depth)))) |
|
3452 |
;; Make sure trailing newlines in END appear in the output by |
|
3453 |
;; setting `:post-blank' property to their number. |
|
3454 |
(when end |
|
3455 |
(org-element-put-property |
|
3456 |
plain-list :post-blank (org-list--trailing-newlines end))) |
|
3457 |
;; Build output. |
|
3458 |
(concat (and start (concat start "\n")) |
|
3459 |
(if (or start end splice (not backend)) |
|
3460 |
contents |
|
3461 |
(org-export-with-backend backend plain-list contents info)) |
|
3462 |
end))))) |
|
3463 |
|
|
3464 |
(defun org-list--to-generic-item (params) |
|
3465 |
"Return a transcoder for `item' elements. |
|
3466 |
PARAMS is a plist used to tweak the behavior of the transcoder." |
|
3467 |
(let ((backend (plist-get params :backend)) |
|
3468 |
(istart (plist-get params :istart)) |
|
3469 |
(iend (plist-get params :iend)) |
|
3470 |
(isep (plist-get params :isep)) |
|
3471 |
(icount (plist-get params :icount)) |
|
3472 |
(ifmt (plist-get params :ifmt)) |
|
3473 |
(cboff (plist-get params :cboff)) |
|
3474 |
(cbon (plist-get params :cbon)) |
|
3475 |
(cbtrans (plist-get params :cbtrans)) |
|
3476 |
(dtstart (plist-get params :dtstart)) |
|
3477 |
(dtend (plist-get params :dtend)) |
|
3478 |
(ddstart (plist-get params :ddstart)) |
|
3479 |
(ddend (plist-get params :ddend))) |
|
3480 |
(lambda (item contents info) |
|
3481 |
(let* ((type |
|
3482 |
(org-element-property :type (org-element-property :parent item))) |
|
3483 |
(tag (org-element-property :tag item)) |
|
3484 |
(depth (org-list--depth item)) |
|
3485 |
(separator (and (org-export-get-next-element item info) |
|
3486 |
(org-list--generic-eval isep type depth))) |
|
3487 |
(closing (pcase (org-list--generic-eval iend type depth) |
|
3488 |
((or `nil "") "\n") |
|
3489 |
((and (guard separator) s) |
|
3490 |
(if (equal (substring s -1) "\n") s (concat s "\n"))) |
|
3491 |
(s s)))) |
|
3492 |
;; When a closing line or a separator is provided, make sure |
|
3493 |
;; its trailing newlines are taken into account when building |
|
3494 |
;; output. This is done by setting `:post-blank' property to |
|
3495 |
;; the number of such lines in the last line to be added. |
|
3496 |
(let ((last-string (or separator closing))) |
|
3497 |
(when last-string |
|
3498 |
(org-element-put-property |
|
3499 |
item |
|
3500 |
:post-blank |
|
3501 |
(max (1- (org-list--trailing-newlines last-string)) 0)))) |
|
3502 |
;; Build output. |
|
3503 |
(concat |
|
3504 |
(let ((c (org-element-property :counter item))) |
|
3505 |
(if (and c icount) (org-list--generic-eval icount type depth c) |
|
3506 |
(org-list--generic-eval istart type depth))) |
|
3507 |
(let ((body |
|
3508 |
(if (or istart iend icount ifmt cbon cboff cbtrans (not backend) |
|
3509 |
(and (eq type 'descriptive) |
|
3510 |
(or dtstart dtend ddstart ddend))) |
|
3511 |
(concat |
|
3512 |
(pcase (org-element-property :checkbox item) |
|
3513 |
(`on cbon) |
|
3514 |
(`off cboff) |
|
3515 |
(`trans cbtrans)) |
|
3516 |
(and tag |
|
3517 |
(concat dtstart |
|
3518 |
(if backend |
|
3519 |
(org-export-data-with-backend |
|
3520 |
tag backend info) |
|
3521 |
(org-element-interpret-data tag)) |
|
3522 |
dtend)) |
|
3523 |
(and tag ddstart) |
|
3524 |
(let ((contents |
|
3525 |
(if (= (length contents) 0) "" |
|
3526 |
(substring contents 0 -1)))) |
|
3527 |
(if ifmt (org-list--generic-eval ifmt type contents) |
|
3528 |
contents)) |
|
3529 |
(and tag ddend)) |
|
3530 |
(org-export-with-backend backend item contents info)))) |
|
3531 |
;; Remove final newline. |
|
3532 |
(if (equal body "") "" |
|
3533 |
(substring (org-element-normalize-string body) 0 -1))) |
|
3534 |
closing |
|
3535 |
separator))))) |
|
3536 |
|
|
3537 |
(defun org-list-to-latex (list &optional params) |
|
3538 |
"Convert LIST into a LaTeX list. |
|
3539 |
LIST is a parsed plain list, as returned by `org-list-to-lisp'. |
|
3540 |
PARAMS is a property list with overruling parameters for |
|
3541 |
`org-list-to-generic'. Return converted list as a string." |
|
3542 |
(require 'ox-latex) |
|
3543 |
(org-list-to-generic list (org-combine-plists '(:backend latex) params))) |
|
3544 |
|
|
3545 |
(defun org-list-to-html (list &optional params) |
|
3546 |
"Convert LIST into a HTML list. |
|
3547 |
LIST is a parsed plain list, as returned by `org-list-to-lisp'. |
|
3548 |
PARAMS is a property list with overruling parameters for |
|
3549 |
`org-list-to-generic'. Return converted list as a string." |
|
3550 |
(require 'ox-html) |
|
3551 |
(org-list-to-generic list (org-combine-plists '(:backend html) params))) |
|
3552 |
|
|
3553 |
(defun org-list-to-texinfo (list &optional params) |
|
3554 |
"Convert LIST into a Texinfo list. |
|
3555 |
LIST is a parsed plain list, as returned by `org-list-to-lisp'. |
|
3556 |
PARAMS is a property list with overruling parameters for |
|
3557 |
`org-list-to-generic'. Return converted list as a string." |
|
3558 |
(require 'ox-texinfo) |
|
3559 |
(org-list-to-generic list (org-combine-plists '(:backend texinfo) params))) |
|
3560 |
|
|
3561 |
(defun org-list-to-org (list &optional params) |
|
3562 |
"Convert LIST into an Org plain list. |
|
3563 |
LIST is as returned by `org-list-parse-list'. PARAMS is a property list |
|
3564 |
with overruling parameters for `org-list-to-generic'." |
|
3565 |
(let* ((make-item |
|
3566 |
(lambda (type _depth &optional c) |
|
3567 |
(concat (if (eq type 'ordered) "1. " "- ") |
|
3568 |
(and c (format "[@%d] " c))))) |
|
3569 |
(defaults |
|
3570 |
(list :istart make-item |
|
3571 |
:icount make-item |
|
3572 |
:ifmt (lambda (_type contents) |
|
3573 |
(replace-regexp-in-string "\n" "\n " contents)) |
|
3574 |
:dtend " :: " |
|
3575 |
:cbon "[X] " |
|
3576 |
:cboff "[ ] " |
|
3577 |
:cbtrans "[-] "))) |
|
3578 |
(org-list-to-generic list (org-combine-plists defaults params)))) |
|
3579 |
|
|
3580 |
(defun org-list-to-subtree (list &optional params) |
|
3581 |
"Convert LIST into an Org subtree. |
|
3582 |
LIST is as returned by `org-list-to-lisp'. PARAMS is a property |
|
3583 |
list with overruling parameters for `org-list-to-generic'." |
|
3584 |
(let* ((blank (pcase (cdr (assq 'heading org-blank-before-new-entry)) |
|
3585 |
(`t t) |
|
3586 |
(`auto (save-excursion |
|
3587 |
(org-with-limited-levels (outline-previous-heading)) |
|
3588 |
(org-previous-line-empty-p))))) |
|
3589 |
(level (org-reduced-level (or (org-current-level) 0))) |
|
3590 |
(make-stars |
|
3591 |
(lambda (_type depth &optional _count) |
|
3592 |
;; Return the string for the heading, depending on DEPTH |
|
3593 |
;; of current sub-list. |
|
3594 |
(let ((oddeven-level (+ level depth))) |
|
3595 |
(concat (make-string (if org-odd-levels-only |
|
3596 |
(1- (* 2 oddeven-level)) |
|
3597 |
oddeven-level) |
|
3598 |
?*) |
|
3599 |
" "))))) |
|
3600 |
(org-list-to-generic |
|
3601 |
list |
|
3602 |
(org-combine-plists |
|
3603 |
(list :splice t |
|
3604 |
:istart make-stars |
|
3605 |
:icount make-stars |
|
3606 |
:dtstart " " :dtend " " |
|
3607 |
:isep (if blank "\n\n" "\n") |
|
3608 |
:cbon "DONE " :cboff "TODO " :cbtrans "TODO ") |
|
3609 |
params)))) |
|
3610 |
|
|
3611 |
(provide 'org-list) |
|
3612 |
|
|
3613 |
;;; org-list.el ends here |