commit | author | age
|
76bbd0
|
1 |
;;; org-element.el --- Parser for Org Syntax -*- lexical-binding: t; -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2012-2018 Free Software Foundation, Inc. |
|
4 |
|
|
5 |
;; Author: Nicolas Goaziou <n.goaziou at gmail dot com> |
|
6 |
;; Keywords: outlines, hypermedia, calendar, wp |
|
7 |
|
|
8 |
;; This file is part of GNU Emacs. |
|
9 |
|
|
10 |
;; GNU Emacs is free software: you can redistribute it and/or modify |
|
11 |
;; it under the terms of the GNU General Public License as published by |
|
12 |
;; the Free Software Foundation, either version 3 of the License, or |
|
13 |
;; (at your option) any later version. |
|
14 |
|
|
15 |
;; GNU Emacs is distributed in the hope that it will be useful, |
|
16 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
17 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
18 |
;; GNU General Public License for more details. |
|
19 |
|
|
20 |
;; You should have received a copy of the GNU General Public License |
|
21 |
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. |
|
22 |
|
|
23 |
;;; Commentary: |
|
24 |
;; |
|
25 |
;; See <https://orgmode.org/worg/dev/org-syntax.html> for details about |
|
26 |
;; Org syntax. |
|
27 |
;; |
|
28 |
;; Lisp-wise, a syntax object can be represented as a list. |
|
29 |
;; It follows the pattern (TYPE PROPERTIES CONTENTS), where: |
|
30 |
;; TYPE is a symbol describing the object. |
|
31 |
;; PROPERTIES is the property list attached to it. See docstring of |
|
32 |
;; appropriate parsing function to get an exhaustive list. |
|
33 |
;; CONTENTS is a list of syntax objects or raw strings contained |
|
34 |
;; in the current object, when applicable. |
|
35 |
;; |
|
36 |
;; For the whole document, TYPE is `org-data' and PROPERTIES is nil. |
|
37 |
;; |
|
38 |
;; The first part of this file defines constants for the Org syntax, |
|
39 |
;; while the second one provide accessors and setters functions. |
|
40 |
;; |
|
41 |
;; The next part implements a parser and an interpreter for each |
|
42 |
;; element and object type in Org syntax. |
|
43 |
;; |
|
44 |
;; The following part creates a fully recursive buffer parser. It |
|
45 |
;; also provides a tool to map a function to elements or objects |
|
46 |
;; matching some criteria in the parse tree. Functions of interest |
|
47 |
;; are `org-element-parse-buffer', `org-element-map' and, to a lesser |
|
48 |
;; extent, `org-element-parse-secondary-string'. |
|
49 |
;; |
|
50 |
;; The penultimate part is the cradle of an interpreter for the |
|
51 |
;; obtained parse tree: `org-element-interpret-data'. |
|
52 |
;; |
|
53 |
;; The library ends by furnishing `org-element-at-point' function, and |
|
54 |
;; a way to give information about document structure around point |
|
55 |
;; with `org-element-context'. A cache mechanism is also provided for |
|
56 |
;; these functions. |
|
57 |
|
|
58 |
|
|
59 |
;;; Code: |
|
60 |
|
|
61 |
(require 'org) |
|
62 |
(require 'avl-tree) |
|
63 |
(require 'cl-lib) |
|
64 |
|
|
65 |
|
|
66 |
|
|
67 |
;;; Definitions And Rules |
|
68 |
;; |
|
69 |
;; Define elements, greater elements and specify recursive objects, |
|
70 |
;; along with the affiliated keywords recognized. Also set up |
|
71 |
;; restrictions on recursive objects combinations. |
|
72 |
;; |
|
73 |
;; `org-element-update-syntax' builds proper syntax regexps according |
|
74 |
;; to current setup. |
|
75 |
|
|
76 |
(defvar org-element-paragraph-separate nil |
|
77 |
"Regexp to separate paragraphs in an Org buffer. |
|
78 |
In the case of lines starting with \"#\" and \":\", this regexp |
|
79 |
is not sufficient to know if point is at a paragraph ending. See |
|
80 |
`org-element-paragraph-parser' for more information.") |
|
81 |
|
|
82 |
(defvar org-element--object-regexp nil |
|
83 |
"Regexp possibly matching the beginning of an object. |
|
84 |
This regexp allows false positives. Dedicated parser (e.g., |
|
85 |
`org-export-bold-parser') will take care of further filtering. |
|
86 |
Radio links are not matched by this regexp, as they are treated |
|
87 |
specially in `org-element--object-lex'.") |
|
88 |
|
|
89 |
(defun org-element--set-regexps () |
|
90 |
"Build variable syntax regexps." |
|
91 |
(setq org-element-paragraph-separate |
|
92 |
(concat "^\\(?:" |
|
93 |
;; Headlines, inlinetasks. |
|
94 |
org-outline-regexp "\\|" |
|
95 |
;; Footnote definitions. |
|
96 |
"\\[fn:[-_[:word:]]+\\]" "\\|" |
|
97 |
;; Diary sexps. |
|
98 |
"%%(" "\\|" |
|
99 |
"[ \t]*\\(?:" |
|
100 |
;; Empty lines. |
|
101 |
"$" "\\|" |
|
102 |
;; Tables (any type). |
|
103 |
"|" "\\|" |
|
104 |
"\\+\\(?:-+\\+\\)+[ \t]*$" "\\|" |
|
105 |
;; Comments, keyword-like or block-like constructs. |
|
106 |
;; Blocks and keywords with dual values need to be |
|
107 |
;; double-checked. |
|
108 |
"#\\(?: \\|$\\|\\+\\(?:" |
|
109 |
"BEGIN_\\S-+" "\\|" |
|
110 |
"\\S-+\\(?:\\[.*\\]\\)?:[ \t]*\\)\\)" |
|
111 |
"\\|" |
|
112 |
;; Drawers (any type) and fixed-width areas. Drawers |
|
113 |
;; need to be double-checked. |
|
114 |
":\\(?: \\|$\\|[-_[:word:]]+:[ \t]*$\\)" "\\|" |
|
115 |
;; Horizontal rules. |
|
116 |
"-\\{5,\\}[ \t]*$" "\\|" |
|
117 |
;; LaTeX environments. |
|
118 |
"\\\\begin{\\([A-Za-z0-9*]+\\)}" "\\|" |
|
119 |
;; Clock lines. |
|
120 |
(regexp-quote org-clock-string) "\\|" |
|
121 |
;; Lists. |
|
122 |
(let ((term (pcase org-plain-list-ordered-item-terminator |
|
123 |
(?\) ")") (?. "\\.") (_ "[.)]"))) |
|
124 |
(alpha (and org-list-allow-alphabetical "\\|[A-Za-z]"))) |
|
125 |
(concat "\\(?:[-+*]\\|\\(?:[0-9]+" alpha "\\)" term "\\)" |
|
126 |
"\\(?:[ \t]\\|$\\)")) |
|
127 |
"\\)\\)") |
|
128 |
org-element--object-regexp |
|
129 |
(mapconcat #'identity |
|
130 |
(let ((link-types (regexp-opt (org-link-types)))) |
|
131 |
(list |
|
132 |
;; Sub/superscript. |
|
133 |
"\\(?:[_^][-{(*+.,[:alnum:]]\\)" |
|
134 |
;; Bold, code, italic, strike-through, underline |
|
135 |
;; and verbatim. |
|
136 |
(concat "[*~=+_/]" |
|
137 |
(format "[^%s]" |
|
138 |
(nth 2 org-emphasis-regexp-components))) |
|
139 |
;; Plain links. |
|
140 |
(concat "\\<" link-types ":") |
|
141 |
;; Objects starting with "[": regular link, |
|
142 |
;; footnote reference, statistics cookie, |
|
143 |
;; timestamp (inactive). |
|
144 |
(concat "\\[\\(?:" |
|
145 |
"fn:" "\\|" |
|
146 |
"\\[" "\\|" |
|
147 |
"[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" "\\|" |
|
148 |
"[0-9]*\\(?:%\\|/[0-9]*\\)\\]" |
|
149 |
"\\)") |
|
150 |
;; Objects starting with "@": export snippets. |
|
151 |
"@@" |
|
152 |
;; Objects starting with "{": macro. |
|
153 |
"{{{" |
|
154 |
;; Objects starting with "<" : timestamp |
|
155 |
;; (active, diary), target, radio target and |
|
156 |
;; angular links. |
|
157 |
(concat "<\\(?:%%\\|<\\|[0-9]\\|" link-types "\\)") |
|
158 |
;; Objects starting with "$": latex fragment. |
|
159 |
"\\$" |
|
160 |
;; Objects starting with "\": line break, |
|
161 |
;; entity, latex fragment. |
|
162 |
"\\\\\\(?:[a-zA-Z[(]\\|\\\\[ \t]*$\\|_ +\\)" |
|
163 |
;; Objects starting with raw text: inline Babel |
|
164 |
;; source block, inline Babel call. |
|
165 |
"\\(?:call\\|src\\)_")) |
|
166 |
"\\|"))) |
|
167 |
|
|
168 |
(org-element--set-regexps) |
|
169 |
|
|
170 |
;;;###autoload |
|
171 |
(defun org-element-update-syntax () |
|
172 |
"Update parser internals." |
|
173 |
(interactive) |
|
174 |
(org-element--set-regexps) |
|
175 |
(org-element-cache-reset 'all)) |
|
176 |
|
|
177 |
(defconst org-element-all-elements |
|
178 |
'(babel-call center-block clock comment comment-block diary-sexp drawer |
|
179 |
dynamic-block example-block export-block fixed-width |
|
180 |
footnote-definition headline horizontal-rule inlinetask item |
|
181 |
keyword latex-environment node-property paragraph plain-list |
|
182 |
planning property-drawer quote-block section |
|
183 |
special-block src-block table table-row verse-block) |
|
184 |
"Complete list of element types.") |
|
185 |
|
|
186 |
(defconst org-element-greater-elements |
|
187 |
'(center-block drawer dynamic-block footnote-definition headline inlinetask |
|
188 |
item plain-list property-drawer quote-block section |
|
189 |
special-block table) |
|
190 |
"List of recursive element types aka Greater Elements.") |
|
191 |
|
|
192 |
(defconst org-element-all-objects |
|
193 |
'(bold code entity export-snippet footnote-reference inline-babel-call |
|
194 |
inline-src-block italic line-break latex-fragment link macro |
|
195 |
radio-target statistics-cookie strike-through subscript superscript |
|
196 |
table-cell target timestamp underline verbatim) |
|
197 |
"Complete list of object types.") |
|
198 |
|
|
199 |
(defconst org-element-recursive-objects |
|
200 |
'(bold footnote-reference italic link subscript radio-target strike-through |
|
201 |
superscript table-cell underline) |
|
202 |
"List of recursive object types.") |
|
203 |
|
|
204 |
(defconst org-element-object-containers |
|
205 |
(append org-element-recursive-objects '(paragraph table-row verse-block)) |
|
206 |
"List of object or element types that can directly contain objects.") |
|
207 |
|
|
208 |
(defconst org-element-affiliated-keywords |
|
209 |
'("CAPTION" "DATA" "HEADER" "HEADERS" "LABEL" "NAME" "PLOT" "RESNAME" "RESULT" |
|
210 |
"RESULTS" "SOURCE" "SRCNAME" "TBLNAME") |
|
211 |
"List of affiliated keywords as strings. |
|
212 |
By default, all keywords setting attributes (e.g., \"ATTR_LATEX\") |
|
213 |
are affiliated keywords and need not to be in this list.") |
|
214 |
|
|
215 |
(defconst org-element-keyword-translation-alist |
|
216 |
'(("DATA" . "NAME") ("LABEL" . "NAME") ("RESNAME" . "NAME") |
|
217 |
("SOURCE" . "NAME") ("SRCNAME" . "NAME") ("TBLNAME" . "NAME") |
|
218 |
("RESULT" . "RESULTS") ("HEADERS" . "HEADER")) |
|
219 |
"Alist of usual translations for keywords. |
|
220 |
The key is the old name and the value the new one. The property |
|
221 |
holding their value will be named after the translated name.") |
|
222 |
|
|
223 |
(defconst org-element-multiple-keywords '("CAPTION" "HEADER") |
|
224 |
"List of affiliated keywords that can occur more than once in an element. |
|
225 |
|
|
226 |
Their value will be consed into a list of strings, which will be |
|
227 |
returned as the value of the property. |
|
228 |
|
|
229 |
This list is checked after translations have been applied. See |
|
230 |
`org-element-keyword-translation-alist'. |
|
231 |
|
|
232 |
By default, all keywords setting attributes (e.g., \"ATTR_LATEX\") |
|
233 |
allow multiple occurrences and need not to be in this list.") |
|
234 |
|
|
235 |
(defconst org-element-parsed-keywords '("CAPTION") |
|
236 |
"List of affiliated keywords whose value can be parsed. |
|
237 |
|
|
238 |
Their value will be stored as a secondary string: a list of |
|
239 |
strings and objects. |
|
240 |
|
|
241 |
This list is checked after translations have been applied. See |
|
242 |
`org-element-keyword-translation-alist'.") |
|
243 |
|
|
244 |
(defconst org-element--parsed-properties-alist |
|
245 |
(mapcar (lambda (k) (cons k (intern (concat ":" (downcase k))))) |
|
246 |
org-element-parsed-keywords) |
|
247 |
"Alist of parsed keywords and associated properties. |
|
248 |
This is generated from `org-element-parsed-keywords', which |
|
249 |
see.") |
|
250 |
|
|
251 |
(defconst org-element-dual-keywords '("CAPTION" "RESULTS") |
|
252 |
"List of affiliated keywords which can have a secondary value. |
|
253 |
|
|
254 |
In Org syntax, they can be written with optional square brackets |
|
255 |
before the colons. For example, RESULTS keyword can be |
|
256 |
associated to a hash value with the following: |
|
257 |
|
|
258 |
#+RESULTS[hash-string]: some-source |
|
259 |
|
|
260 |
This list is checked after translations have been applied. See |
|
261 |
`org-element-keyword-translation-alist'.") |
|
262 |
|
|
263 |
(defconst org-element--affiliated-re |
|
264 |
(format "[ \t]*#\\+\\(?:%s\\):[ \t]*" |
|
265 |
(concat |
|
266 |
;; Dual affiliated keywords. |
|
267 |
(format "\\(?1:%s\\)\\(?:\\[\\(.*\\)\\]\\)?" |
|
268 |
(regexp-opt org-element-dual-keywords)) |
|
269 |
"\\|" |
|
270 |
;; Regular affiliated keywords. |
|
271 |
(format "\\(?1:%s\\)" |
|
272 |
(regexp-opt |
|
273 |
(cl-remove-if |
|
274 |
(lambda (k) (member k org-element-dual-keywords)) |
|
275 |
org-element-affiliated-keywords))) |
|
276 |
"\\|" |
|
277 |
;; Export attributes. |
|
278 |
"\\(?1:ATTR_[-_A-Za-z0-9]+\\)")) |
|
279 |
"Regexp matching any affiliated keyword. |
|
280 |
|
|
281 |
Keyword name is put in match group 1. Moreover, if keyword |
|
282 |
belongs to `org-element-dual-keywords', put the dual value in |
|
283 |
match group 2. |
|
284 |
|
|
285 |
Don't modify it, set `org-element-affiliated-keywords' instead.") |
|
286 |
|
|
287 |
(defconst org-element-object-restrictions |
|
288 |
(let* ((standard-set (remq 'table-cell org-element-all-objects)) |
|
289 |
(standard-set-no-line-break (remq 'line-break standard-set))) |
|
290 |
`((bold ,@standard-set) |
|
291 |
(footnote-reference ,@standard-set) |
|
292 |
(headline ,@standard-set-no-line-break) |
|
293 |
(inlinetask ,@standard-set-no-line-break) |
|
294 |
(italic ,@standard-set) |
|
295 |
(item ,@standard-set-no-line-break) |
|
296 |
(keyword ,@(remq 'footnote-reference standard-set)) |
|
297 |
;; Ignore all links in a link description. Also ignore |
|
298 |
;; radio-targets and line breaks. |
|
299 |
(link bold code entity export-snippet inline-babel-call inline-src-block |
|
300 |
italic latex-fragment macro statistics-cookie strike-through |
|
301 |
subscript superscript underline verbatim) |
|
302 |
(paragraph ,@standard-set) |
|
303 |
;; Remove any variable object from radio target as it would |
|
304 |
;; prevent it from being properly recognized. |
|
305 |
(radio-target bold code entity italic latex-fragment strike-through |
|
306 |
subscript superscript underline superscript) |
|
307 |
(strike-through ,@standard-set) |
|
308 |
(subscript ,@standard-set) |
|
309 |
(superscript ,@standard-set) |
|
310 |
;; Ignore inline babel call and inline src block as formulas are |
|
311 |
;; possible. Also ignore line breaks and statistics cookies. |
|
312 |
(table-cell bold code entity export-snippet footnote-reference italic |
|
313 |
latex-fragment link macro radio-target strike-through |
|
314 |
subscript superscript target timestamp underline verbatim) |
|
315 |
(table-row table-cell) |
|
316 |
(underline ,@standard-set) |
|
317 |
(verse-block ,@standard-set))) |
|
318 |
"Alist of objects restrictions. |
|
319 |
|
|
320 |
key is an element or object type containing objects and value is |
|
321 |
a list of types that can be contained within an element or object |
|
322 |
of such type. |
|
323 |
|
|
324 |
For example, in a `radio-target' object, one can only find |
|
325 |
entities, latex-fragments, subscript, superscript and text |
|
326 |
markup. |
|
327 |
|
|
328 |
This alist also applies to secondary string. For example, an |
|
329 |
`headline' type element doesn't directly contain objects, but |
|
330 |
still has an entry since one of its properties (`:title') does.") |
|
331 |
|
|
332 |
(defconst org-element-secondary-value-alist |
|
333 |
'((headline :title) |
|
334 |
(inlinetask :title) |
|
335 |
(item :tag)) |
|
336 |
"Alist between element types and locations of secondary values.") |
|
337 |
|
|
338 |
(defconst org-element--pair-round-table |
|
339 |
(let ((table (make-syntax-table))) |
|
340 |
(modify-syntax-entry ?\( "()" table) |
|
341 |
(modify-syntax-entry ?\) ")(" table) |
|
342 |
(dolist (char '(?\{ ?\} ?\[ ?\] ?\< ?\>) table) |
|
343 |
(modify-syntax-entry char " " table))) |
|
344 |
"Table used internally to pair only round brackets. |
|
345 |
Other brackets are treated as spaces.") |
|
346 |
|
|
347 |
(defconst org-element--pair-square-table |
|
348 |
(let ((table (make-syntax-table))) |
|
349 |
(modify-syntax-entry ?\[ "(]" table) |
|
350 |
(modify-syntax-entry ?\] ")[" table) |
|
351 |
(dolist (char '(?\{ ?\} ?\( ?\) ?\< ?\>) table) |
|
352 |
(modify-syntax-entry char " " table))) |
|
353 |
"Table used internally to pair only square brackets. |
|
354 |
Other brackets are treated as spaces.") |
|
355 |
|
|
356 |
(defconst org-element--pair-curly-table |
|
357 |
(let ((table (make-syntax-table))) |
|
358 |
(modify-syntax-entry ?\{ "(}" table) |
|
359 |
(modify-syntax-entry ?\} "){" table) |
|
360 |
(dolist (char '(?\[ ?\] ?\( ?\) ?\< ?\>) table) |
|
361 |
(modify-syntax-entry char " " table))) |
|
362 |
"Table used internally to pair only curly brackets. |
|
363 |
Other brackets are treated as spaces.") |
|
364 |
|
|
365 |
(defun org-element--parse-paired-brackets (char) |
|
366 |
"Parse paired brackets at point. |
|
367 |
CHAR is the opening bracket to consider, as a character. Return |
|
368 |
contents between brackets, as a string, or nil. Also move point |
|
369 |
past the brackets." |
|
370 |
(when (eq char (char-after)) |
|
371 |
(let ((syntax-table (pcase char |
|
372 |
(?\{ org-element--pair-curly-table) |
|
373 |
(?\[ org-element--pair-square-table) |
|
374 |
(?\( org-element--pair-round-table) |
|
375 |
(_ nil))) |
|
376 |
(pos (point))) |
|
377 |
(when syntax-table |
|
378 |
(with-syntax-table syntax-table |
|
379 |
(let ((end (ignore-errors (scan-lists pos 1 0)))) |
|
380 |
(when end |
|
381 |
(goto-char end) |
|
382 |
(buffer-substring-no-properties (1+ pos) (1- end))))))))) |
|
383 |
|
|
384 |
|
|
385 |
;;; Accessors and Setters |
|
386 |
;; |
|
387 |
;; Provide four accessors: `org-element-type', `org-element-property' |
|
388 |
;; `org-element-contents' and `org-element-restriction'. |
|
389 |
;; |
|
390 |
;; Setter functions allow modification of elements by side effect. |
|
391 |
;; There is `org-element-put-property', `org-element-set-contents'. |
|
392 |
;; These low-level functions are useful to build a parse tree. |
|
393 |
;; |
|
394 |
;; `org-element-adopt-elements', `org-element-set-element', |
|
395 |
;; `org-element-extract-element' and `org-element-insert-before' are |
|
396 |
;; high-level functions useful to modify a parse tree. |
|
397 |
;; |
|
398 |
;; `org-element-secondary-p' is a predicate used to know if a given |
|
399 |
;; object belongs to a secondary string. `org-element-class' tells if |
|
400 |
;; some parsed data is an element or an object, handling pseudo |
|
401 |
;; elements and objects. `org-element-copy' returns an element or |
|
402 |
;; object, stripping its parent property in the process. |
|
403 |
|
|
404 |
(defsubst org-element-type (element) |
|
405 |
"Return type of ELEMENT. |
|
406 |
|
|
407 |
The function returns the type of the element or object provided. |
|
408 |
It can also return the following special value: |
|
409 |
`plain-text' for a string |
|
410 |
`org-data' for a complete document |
|
411 |
nil in any other case." |
|
412 |
(cond |
|
413 |
((not (consp element)) (and (stringp element) 'plain-text)) |
|
414 |
((symbolp (car element)) (car element)))) |
|
415 |
|
|
416 |
(defsubst org-element-property (property element) |
|
417 |
"Extract the value from the PROPERTY of an ELEMENT." |
|
418 |
(if (stringp element) (get-text-property 0 property element) |
|
419 |
(plist-get (nth 1 element) property))) |
|
420 |
|
|
421 |
(defsubst org-element-contents (element) |
|
422 |
"Extract contents from an ELEMENT." |
|
423 |
(cond ((not (consp element)) nil) |
|
424 |
((symbolp (car element)) (nthcdr 2 element)) |
|
425 |
(t element))) |
|
426 |
|
|
427 |
(defsubst org-element-restriction (element) |
|
428 |
"Return restriction associated to ELEMENT. |
|
429 |
ELEMENT can be an element, an object or a symbol representing an |
|
430 |
element or object type." |
|
431 |
(cdr (assq (if (symbolp element) element (org-element-type element)) |
|
432 |
org-element-object-restrictions))) |
|
433 |
|
|
434 |
(defsubst org-element-put-property (element property value) |
|
435 |
"In ELEMENT set PROPERTY to VALUE. |
|
436 |
Return modified element." |
|
437 |
(if (stringp element) (org-add-props element nil property value) |
|
438 |
(setcar (cdr element) (plist-put (nth 1 element) property value)) |
|
439 |
element)) |
|
440 |
|
|
441 |
(defsubst org-element-set-contents (element &rest contents) |
|
442 |
"Set ELEMENT's contents to CONTENTS. |
|
443 |
Return ELEMENT." |
|
444 |
(cond ((null element) contents) |
|
445 |
((not (symbolp (car element))) contents) |
|
446 |
((cdr element) (setcdr (cdr element) contents) element) |
|
447 |
(t (nconc element contents)))) |
|
448 |
|
|
449 |
(defun org-element-secondary-p (object) |
|
450 |
"Non-nil when OBJECT directly belongs to a secondary string. |
|
451 |
Return value is the property name, as a keyword, or nil." |
|
452 |
(let* ((parent (org-element-property :parent object)) |
|
453 |
(properties (cdr (assq (org-element-type parent) |
|
454 |
org-element-secondary-value-alist)))) |
|
455 |
(catch 'exit |
|
456 |
(dolist (p properties) |
|
457 |
(and (memq object (org-element-property p parent)) |
|
458 |
(throw 'exit p)))))) |
|
459 |
|
|
460 |
(defsubst org-element-class (datum &optional parent) |
|
461 |
"Return class for ELEMENT, as a symbol. |
|
462 |
Class is either `element' or `object'. Optional argument PARENT |
|
463 |
is the element or object containing DATUM. It defaults to the |
|
464 |
value of DATUM `:parent' property." |
|
465 |
(let ((type (org-element-type datum)) |
|
466 |
(parent (or parent (org-element-property :parent datum)))) |
|
467 |
(cond |
|
468 |
;; Trivial cases. |
|
469 |
((memq type org-element-all-objects) 'object) |
|
470 |
((memq type org-element-all-elements) 'element) |
|
471 |
;; Special cases. |
|
472 |
((eq type 'org-data) 'element) |
|
473 |
((eq type 'plain-text) 'object) |
|
474 |
((not type) 'object) |
|
475 |
;; Pseudo object or elements. Make a guess about its class. |
|
476 |
;; Basically a pseudo object is contained within another object, |
|
477 |
;; a secondary string or a container element. |
|
478 |
((not parent) 'element) |
|
479 |
(t |
|
480 |
(let ((parent-type (org-element-type parent))) |
|
481 |
(cond ((not parent-type) 'object) |
|
482 |
((memq parent-type org-element-object-containers) 'object) |
|
483 |
((org-element-secondary-p datum) 'object) |
|
484 |
(t 'element))))))) |
|
485 |
|
|
486 |
(defsubst org-element-adopt-elements (parent &rest children) |
|
487 |
"Append elements to the contents of another element. |
|
488 |
|
|
489 |
PARENT is an element or object. CHILDREN can be elements, |
|
490 |
objects, or a strings. |
|
491 |
|
|
492 |
The function takes care of setting `:parent' property for CHILD. |
|
493 |
Return parent element." |
|
494 |
(if (not children) parent |
|
495 |
;; Link every child to PARENT. If PARENT is nil, it is a secondary |
|
496 |
;; string: parent is the list itself. |
|
497 |
(dolist (child children) |
|
498 |
(org-element-put-property child :parent (or parent children))) |
|
499 |
;; Add CHILDREN at the end of PARENT contents. |
|
500 |
(when parent |
|
501 |
(apply #'org-element-set-contents |
|
502 |
parent |
|
503 |
(nconc (org-element-contents parent) children))) |
|
504 |
;; Return modified PARENT element. |
|
505 |
(or parent children))) |
|
506 |
|
|
507 |
(defun org-element-extract-element (element) |
|
508 |
"Extract ELEMENT from parse tree. |
|
509 |
Remove element from the parse tree by side-effect, and return it |
|
510 |
with its `:parent' property stripped out." |
|
511 |
(let ((parent (org-element-property :parent element)) |
|
512 |
(secondary (org-element-secondary-p element))) |
|
513 |
(if secondary |
|
514 |
(org-element-put-property |
|
515 |
parent secondary |
|
516 |
(delq element (org-element-property secondary parent))) |
|
517 |
(apply #'org-element-set-contents |
|
518 |
parent |
|
519 |
(delq element (org-element-contents parent)))) |
|
520 |
;; Return ELEMENT with its :parent removed. |
|
521 |
(org-element-put-property element :parent nil))) |
|
522 |
|
|
523 |
(defun org-element-insert-before (element location) |
|
524 |
"Insert ELEMENT before LOCATION in parse tree. |
|
525 |
LOCATION is an element, object or string within the parse tree. |
|
526 |
Parse tree is modified by side effect." |
|
527 |
(let* ((parent (org-element-property :parent location)) |
|
528 |
(property (org-element-secondary-p location)) |
|
529 |
(siblings (if property (org-element-property property parent) |
|
530 |
(org-element-contents parent))) |
|
531 |
;; Special case: LOCATION is the first element of an |
|
532 |
;; independent secondary string (e.g. :title property). Add |
|
533 |
;; ELEMENT in-place. |
|
534 |
(specialp (and (not property) |
|
535 |
(eq siblings parent) |
|
536 |
(eq (car parent) location)))) |
|
537 |
;; Install ELEMENT at the appropriate LOCATION within SIBLINGS. |
|
538 |
(cond (specialp) |
|
539 |
((or (null siblings) (eq (car siblings) location)) |
|
540 |
(push element siblings)) |
|
541 |
((null location) (nconc siblings (list element))) |
|
542 |
(t |
|
543 |
(let ((index (cl-position location siblings))) |
|
544 |
(unless index (error "No location found to insert element")) |
|
545 |
(push element (cdr (nthcdr (1- index) siblings)))))) |
|
546 |
;; Store SIBLINGS at appropriate place in parse tree. |
|
547 |
(cond |
|
548 |
(specialp (setcdr parent (copy-sequence parent)) (setcar parent element)) |
|
549 |
(property (org-element-put-property parent property siblings)) |
|
550 |
(t (apply #'org-element-set-contents parent siblings))) |
|
551 |
;; Set appropriate :parent property. |
|
552 |
(org-element-put-property element :parent parent))) |
|
553 |
|
|
554 |
(defun org-element-set-element (old new) |
|
555 |
"Replace element or object OLD with element or object NEW. |
|
556 |
The function takes care of setting `:parent' property for NEW." |
|
557 |
;; Ensure OLD and NEW have the same parent. |
|
558 |
(org-element-put-property new :parent (org-element-property :parent old)) |
|
559 |
(if (or (memq (org-element-type old) '(plain-text nil)) |
|
560 |
(memq (org-element-type new) '(plain-text nil))) |
|
561 |
;; We cannot replace OLD with NEW since one of them is not an |
|
562 |
;; object or element. We take the long path. |
|
563 |
(progn (org-element-insert-before new old) |
|
564 |
(org-element-extract-element old)) |
|
565 |
;; Since OLD is going to be changed into NEW by side-effect, first |
|
566 |
;; make sure that every element or object within NEW has OLD as |
|
567 |
;; parent. |
|
568 |
(dolist (blob (org-element-contents new)) |
|
569 |
(org-element-put-property blob :parent old)) |
|
570 |
;; Transfer contents. |
|
571 |
(apply #'org-element-set-contents old (org-element-contents new)) |
|
572 |
;; Overwrite OLD's properties with NEW's. |
|
573 |
(setcar (cdr old) (nth 1 new)) |
|
574 |
;; Transfer type. |
|
575 |
(setcar old (car new)))) |
|
576 |
|
|
577 |
(defun org-element-create (type &optional props &rest children) |
|
578 |
"Create a new element of type TYPE. |
|
579 |
Optional argument PROPS, when non-nil, is a plist defining the |
|
580 |
properties of the element. CHILDREN can be elements, objects or |
|
581 |
strings." |
|
582 |
(apply #'org-element-adopt-elements (list type props) children)) |
|
583 |
|
|
584 |
(defun org-element-copy (datum) |
|
585 |
"Return a copy of DATUM. |
|
586 |
DATUM is an element, object, string or nil. `:parent' property |
|
587 |
is cleared and contents are removed in the process." |
|
588 |
(when datum |
|
589 |
(let ((type (org-element-type datum))) |
|
590 |
(pcase type |
|
591 |
(`org-data (list 'org-data nil)) |
|
592 |
(`plain-text (substring-no-properties datum)) |
|
593 |
(`nil (copy-sequence datum)) |
|
594 |
(_ |
|
595 |
(list type (plist-put (copy-sequence (nth 1 datum)) :parent nil))))))) |
|
596 |
|
|
597 |
|
|
598 |
|
|
599 |
;;; Greater elements |
|
600 |
;; |
|
601 |
;; For each greater element type, we define a parser and an |
|
602 |
;; interpreter. |
|
603 |
;; |
|
604 |
;; A parser returns the element or object as the list described above. |
|
605 |
;; Most of them accepts no argument. Though, exceptions exist. Hence |
|
606 |
;; every element containing a secondary string (see |
|
607 |
;; `org-element-secondary-value-alist') will accept an optional |
|
608 |
;; argument to toggle parsing of these secondary strings. Moreover, |
|
609 |
;; `item' parser requires current list's structure as its first |
|
610 |
;; element. |
|
611 |
;; |
|
612 |
;; An interpreter accepts two arguments: the list representation of |
|
613 |
;; the element or object, and its contents. The latter may be nil, |
|
614 |
;; depending on the element or object considered. It returns the |
|
615 |
;; appropriate Org syntax, as a string. |
|
616 |
;; |
|
617 |
;; Parsing functions must follow the naming convention: |
|
618 |
;; org-element-TYPE-parser, where TYPE is greater element's type, as |
|
619 |
;; defined in `org-element-greater-elements'. |
|
620 |
;; |
|
621 |
;; Similarly, interpreting functions must follow the naming |
|
622 |
;; convention: org-element-TYPE-interpreter. |
|
623 |
;; |
|
624 |
;; With the exception of `headline' and `item' types, greater elements |
|
625 |
;; cannot contain other greater elements of their own type. |
|
626 |
;; |
|
627 |
;; Beside implementing a parser and an interpreter, adding a new |
|
628 |
;; greater element requires tweaking `org-element--current-element'. |
|
629 |
;; Moreover, the newly defined type must be added to both |
|
630 |
;; `org-element-all-elements' and `org-element-greater-elements'. |
|
631 |
|
|
632 |
|
|
633 |
;;;; Center Block |
|
634 |
|
|
635 |
(defun org-element-center-block-parser (limit affiliated) |
|
636 |
"Parse a center block. |
|
637 |
|
|
638 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
639 |
the buffer position at the beginning of the first affiliated |
|
640 |
keyword and CDR is a plist of affiliated keywords along with |
|
641 |
their value. |
|
642 |
|
|
643 |
Return a list whose CAR is `center-block' and CDR is a plist |
|
644 |
containing `:begin', `:end', `:contents-begin', `:contents-end', |
|
645 |
`:post-blank' and `:post-affiliated' keywords. |
|
646 |
|
|
647 |
Assume point is at the beginning of the block." |
|
648 |
(let ((case-fold-search t)) |
|
649 |
(if (not (save-excursion |
|
650 |
(re-search-forward "^[ \t]*#\\+END_CENTER[ \t]*$" limit t))) |
|
651 |
;; Incomplete block: parse it as a paragraph. |
|
652 |
(org-element-paragraph-parser limit affiliated) |
|
653 |
(let ((block-end-line (match-beginning 0))) |
|
654 |
(let* ((begin (car affiliated)) |
|
655 |
(post-affiliated (point)) |
|
656 |
;; Empty blocks have no contents. |
|
657 |
(contents-begin (progn (forward-line) |
|
658 |
(and (< (point) block-end-line) |
|
659 |
(point)))) |
|
660 |
(contents-end (and contents-begin block-end-line)) |
|
661 |
(pos-before-blank (progn (goto-char block-end-line) |
|
662 |
(forward-line) |
|
663 |
(point))) |
|
664 |
(end (save-excursion |
|
665 |
(skip-chars-forward " \r\t\n" limit) |
|
666 |
(if (eobp) (point) (line-beginning-position))))) |
|
667 |
(list 'center-block |
|
668 |
(nconc |
|
669 |
(list :begin begin |
|
670 |
:end end |
|
671 |
:contents-begin contents-begin |
|
672 |
:contents-end contents-end |
|
673 |
:post-blank (count-lines pos-before-blank end) |
|
674 |
:post-affiliated post-affiliated) |
|
675 |
(cdr affiliated)))))))) |
|
676 |
|
|
677 |
(defun org-element-center-block-interpreter (_ contents) |
|
678 |
"Interpret a center-block element as Org syntax. |
|
679 |
CONTENTS is the contents of the element." |
|
680 |
(format "#+BEGIN_CENTER\n%s#+END_CENTER" contents)) |
|
681 |
|
|
682 |
|
|
683 |
;;;; Drawer |
|
684 |
|
|
685 |
(defun org-element-drawer-parser (limit affiliated) |
|
686 |
"Parse a drawer. |
|
687 |
|
|
688 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
689 |
the buffer position at the beginning of the first affiliated |
|
690 |
keyword and CDR is a plist of affiliated keywords along with |
|
691 |
their value. |
|
692 |
|
|
693 |
Return a list whose CAR is `drawer' and CDR is a plist containing |
|
694 |
`:drawer-name', `:begin', `:end', `:contents-begin', |
|
695 |
`:contents-end', `:post-blank' and `:post-affiliated' keywords. |
|
696 |
|
|
697 |
Assume point is at beginning of drawer." |
|
698 |
(let ((case-fold-search t)) |
|
699 |
(if (not (save-excursion (re-search-forward "^[ \t]*:END:[ \t]*$" limit t))) |
|
700 |
;; Incomplete drawer: parse it as a paragraph. |
|
701 |
(org-element-paragraph-parser limit affiliated) |
|
702 |
(save-excursion |
|
703 |
(let* ((drawer-end-line (match-beginning 0)) |
|
704 |
(name (progn (looking-at org-drawer-regexp) |
|
705 |
(match-string-no-properties 1))) |
|
706 |
(begin (car affiliated)) |
|
707 |
(post-affiliated (point)) |
|
708 |
;; Empty drawers have no contents. |
|
709 |
(contents-begin (progn (forward-line) |
|
710 |
(and (< (point) drawer-end-line) |
|
711 |
(point)))) |
|
712 |
(contents-end (and contents-begin drawer-end-line)) |
|
713 |
(pos-before-blank (progn (goto-char drawer-end-line) |
|
714 |
(forward-line) |
|
715 |
(point))) |
|
716 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
717 |
(if (eobp) (point) (line-beginning-position))))) |
|
718 |
(list 'drawer |
|
719 |
(nconc |
|
720 |
(list :begin begin |
|
721 |
:end end |
|
722 |
:drawer-name name |
|
723 |
:contents-begin contents-begin |
|
724 |
:contents-end contents-end |
|
725 |
:post-blank (count-lines pos-before-blank end) |
|
726 |
:post-affiliated post-affiliated) |
|
727 |
(cdr affiliated)))))))) |
|
728 |
|
|
729 |
(defun org-element-drawer-interpreter (drawer contents) |
|
730 |
"Interpret DRAWER element as Org syntax. |
|
731 |
CONTENTS is the contents of the element." |
|
732 |
(format ":%s:\n%s:END:" |
|
733 |
(org-element-property :drawer-name drawer) |
|
734 |
contents)) |
|
735 |
|
|
736 |
|
|
737 |
;;;; Dynamic Block |
|
738 |
|
|
739 |
(defun org-element-dynamic-block-parser (limit affiliated) |
|
740 |
"Parse a dynamic block. |
|
741 |
|
|
742 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
743 |
the buffer position at the beginning of the first affiliated |
|
744 |
keyword and CDR is a plist of affiliated keywords along with |
|
745 |
their value. |
|
746 |
|
|
747 |
Return a list whose CAR is `dynamic-block' and CDR is a plist |
|
748 |
containing `:block-name', `:begin', `:end', `:contents-begin', |
|
749 |
`:contents-end', `:arguments', `:post-blank' and |
|
750 |
`:post-affiliated' keywords. |
|
751 |
|
|
752 |
Assume point is at beginning of dynamic block." |
|
753 |
(let ((case-fold-search t)) |
|
754 |
(if (not (save-excursion |
|
755 |
(re-search-forward "^[ \t]*#\\+END:?[ \t]*$" limit t))) |
|
756 |
;; Incomplete block: parse it as a paragraph. |
|
757 |
(org-element-paragraph-parser limit affiliated) |
|
758 |
(let ((block-end-line (match-beginning 0))) |
|
759 |
(save-excursion |
|
760 |
(let* ((name (progn (looking-at org-dblock-start-re) |
|
761 |
(match-string-no-properties 1))) |
|
762 |
(arguments (match-string-no-properties 3)) |
|
763 |
(begin (car affiliated)) |
|
764 |
(post-affiliated (point)) |
|
765 |
;; Empty blocks have no contents. |
|
766 |
(contents-begin (progn (forward-line) |
|
767 |
(and (< (point) block-end-line) |
|
768 |
(point)))) |
|
769 |
(contents-end (and contents-begin block-end-line)) |
|
770 |
(pos-before-blank (progn (goto-char block-end-line) |
|
771 |
(forward-line) |
|
772 |
(point))) |
|
773 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
774 |
(if (eobp) (point) (line-beginning-position))))) |
|
775 |
(list 'dynamic-block |
|
776 |
(nconc |
|
777 |
(list :begin begin |
|
778 |
:end end |
|
779 |
:block-name name |
|
780 |
:arguments arguments |
|
781 |
:contents-begin contents-begin |
|
782 |
:contents-end contents-end |
|
783 |
:post-blank (count-lines pos-before-blank end) |
|
784 |
:post-affiliated post-affiliated) |
|
785 |
(cdr affiliated))))))))) |
|
786 |
|
|
787 |
(defun org-element-dynamic-block-interpreter (dynamic-block contents) |
|
788 |
"Interpret DYNAMIC-BLOCK element as Org syntax. |
|
789 |
CONTENTS is the contents of the element." |
|
790 |
(format "#+BEGIN: %s%s\n%s#+END:" |
|
791 |
(org-element-property :block-name dynamic-block) |
|
792 |
(let ((args (org-element-property :arguments dynamic-block))) |
|
793 |
(if args (concat " " args) "")) |
|
794 |
contents)) |
|
795 |
|
|
796 |
|
|
797 |
;;;; Footnote Definition |
|
798 |
|
|
799 |
(defconst org-element--footnote-separator |
|
800 |
(concat org-outline-regexp-bol "\\|" |
|
801 |
org-footnote-definition-re "\\|" |
|
802 |
"^\\([ \t]*\n\\)\\{2,\\}") |
|
803 |
"Regexp used as a footnote definition separator.") |
|
804 |
|
|
805 |
(defun org-element-footnote-definition-parser (limit affiliated) |
|
806 |
"Parse a footnote definition. |
|
807 |
|
|
808 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
809 |
the buffer position at the beginning of the first affiliated |
|
810 |
keyword and CDR is a plist of affiliated keywords along with |
|
811 |
their value. |
|
812 |
|
|
813 |
Return a list whose CAR is `footnote-definition' and CDR is |
|
814 |
a plist containing `:label', `:begin' `:end', `:contents-begin', |
|
815 |
`:contents-end', `:post-blank' and `:post-affiliated' keywords. |
|
816 |
|
|
817 |
Assume point is at the beginning of the footnote definition." |
|
818 |
(save-excursion |
|
819 |
(let* ((label (progn (looking-at org-footnote-definition-re) |
|
820 |
(match-string-no-properties 1))) |
|
821 |
(begin (car affiliated)) |
|
822 |
(post-affiliated (point)) |
|
823 |
(end |
|
824 |
(save-excursion |
|
825 |
(end-of-line) |
|
826 |
(cond |
|
827 |
((not |
|
828 |
(re-search-forward org-element--footnote-separator limit t)) |
|
829 |
limit) |
|
830 |
((eq ?\[ (char-after (match-beginning 0))) |
|
831 |
;; At a new footnote definition, make sure we end |
|
832 |
;; before any affiliated keyword above. |
|
833 |
(forward-line -1) |
|
834 |
(while (and (> (point) post-affiliated) |
|
835 |
(looking-at-p org-element--affiliated-re)) |
|
836 |
(forward-line -1)) |
|
837 |
(line-beginning-position 2)) |
|
838 |
((eq ?* (char-after (match-beginning 0))) (match-beginning 0)) |
|
839 |
(t (skip-chars-forward " \r\t\n" limit) |
|
840 |
(if (= limit (point)) limit (line-beginning-position)))))) |
|
841 |
(contents-begin |
|
842 |
(progn (search-forward "]") |
|
843 |
(skip-chars-forward " \r\t\n" end) |
|
844 |
(cond ((= (point) end) nil) |
|
845 |
((= (line-beginning-position) post-affiliated) (point)) |
|
846 |
(t (line-beginning-position))))) |
|
847 |
(contents-end |
|
848 |
(progn (goto-char end) |
|
849 |
(skip-chars-backward " \r\t\n") |
|
850 |
(line-beginning-position 2)))) |
|
851 |
(list 'footnote-definition |
|
852 |
(nconc |
|
853 |
(list :label label |
|
854 |
:begin begin |
|
855 |
:end end |
|
856 |
:contents-begin contents-begin |
|
857 |
:contents-end (and contents-begin contents-end) |
|
858 |
:post-blank (count-lines contents-end end) |
|
859 |
:post-affiliated post-affiliated) |
|
860 |
(cdr affiliated)))))) |
|
861 |
|
|
862 |
(defun org-element-footnote-definition-interpreter (footnote-definition contents) |
|
863 |
"Interpret FOOTNOTE-DEFINITION element as Org syntax. |
|
864 |
CONTENTS is the contents of the footnote-definition." |
|
865 |
(concat (format "[fn:%s]" (org-element-property :label footnote-definition)) |
|
866 |
" " |
|
867 |
contents)) |
|
868 |
|
|
869 |
|
|
870 |
;;;; Headline |
|
871 |
|
|
872 |
(defun org-element--get-node-properties () |
|
873 |
"Return node properties associated to headline at point. |
|
874 |
Upcase property names. It avoids confusion between properties |
|
875 |
obtained through property drawer and default properties from the |
|
876 |
parser (e.g. `:end' and :END:). Return value is a plist." |
|
877 |
(save-excursion |
|
878 |
(forward-line) |
|
879 |
(when (looking-at-p org-planning-line-re) (forward-line)) |
|
880 |
(when (looking-at org-property-drawer-re) |
|
881 |
(forward-line) |
|
882 |
(let ((end (match-end 0)) properties) |
|
883 |
(while (< (line-end-position) end) |
|
884 |
(looking-at org-property-re) |
|
885 |
(push (match-string-no-properties 3) properties) |
|
886 |
(push (intern (concat ":" (upcase (match-string 2)))) properties) |
|
887 |
(forward-line)) |
|
888 |
properties)))) |
|
889 |
|
|
890 |
(defun org-element--get-time-properties () |
|
891 |
"Return time properties associated to headline at point. |
|
892 |
Return value is a plist." |
|
893 |
(save-excursion |
|
894 |
(when (progn (forward-line) (looking-at org-planning-line-re)) |
|
895 |
(let ((end (line-end-position)) plist) |
|
896 |
(while (re-search-forward org-keyword-time-not-clock-regexp end t) |
|
897 |
(goto-char (match-end 1)) |
|
898 |
(skip-chars-forward " \t") |
|
899 |
(let ((keyword (match-string 1)) |
|
900 |
(time (org-element-timestamp-parser))) |
|
901 |
(cond ((equal keyword org-scheduled-string) |
|
902 |
(setq plist (plist-put plist :scheduled time))) |
|
903 |
((equal keyword org-deadline-string) |
|
904 |
(setq plist (plist-put plist :deadline time))) |
|
905 |
(t (setq plist (plist-put plist :closed time)))))) |
|
906 |
plist)))) |
|
907 |
|
|
908 |
(defun org-element-headline-parser (limit &optional raw-secondary-p) |
|
909 |
"Parse a headline. |
|
910 |
|
|
911 |
Return a list whose CAR is `headline' and CDR is a plist |
|
912 |
containing `:raw-value', `:title', `:begin', `:end', |
|
913 |
`:pre-blank', `:contents-begin' and `:contents-end', `:level', |
|
914 |
`:priority', `:tags', `:todo-keyword',`:todo-type', `:scheduled', |
|
915 |
`:deadline', `:closed', `:archivedp', `:commentedp' |
|
916 |
`:footnote-section-p', `:post-blank' and `:post-affiliated' |
|
917 |
keywords. |
|
918 |
|
|
919 |
The plist also contains any property set in the property drawer, |
|
920 |
with its name in upper cases and colons added at the |
|
921 |
beginning (e.g., `:CUSTOM_ID'). |
|
922 |
|
|
923 |
LIMIT is a buffer position bounding the search. |
|
924 |
|
|
925 |
When RAW-SECONDARY-P is non-nil, headline's title will not be |
|
926 |
parsed as a secondary string, but as a plain string instead. |
|
927 |
|
|
928 |
Assume point is at beginning of the headline." |
|
929 |
(save-excursion |
|
930 |
(let* ((begin (point)) |
|
931 |
(level (prog1 (org-reduced-level (skip-chars-forward "*")) |
|
932 |
(skip-chars-forward " \t"))) |
|
933 |
(todo (and org-todo-regexp |
|
934 |
(let (case-fold-search) (looking-at (concat org-todo-regexp " "))) |
|
935 |
(progn (goto-char (match-end 0)) |
|
936 |
(skip-chars-forward " \t") |
|
937 |
(match-string 1)))) |
|
938 |
(todo-type |
|
939 |
(and todo (if (member todo org-done-keywords) 'done 'todo))) |
|
940 |
(priority (and (looking-at "\\[#.\\][ \t]*") |
|
941 |
(progn (goto-char (match-end 0)) |
|
942 |
(aref (match-string 0) 2)))) |
|
943 |
(commentedp |
|
944 |
(and (let (case-fold-search) (looking-at org-comment-string)) |
|
945 |
(goto-char (match-end 0)))) |
|
946 |
(title-start (point)) |
|
947 |
(tags (when (re-search-forward |
|
948 |
"[ \t]+\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" |
|
949 |
(line-end-position) |
|
950 |
'move) |
|
951 |
(goto-char (match-beginning 0)) |
|
952 |
(org-split-string (match-string 1) ":"))) |
|
953 |
(title-end (point)) |
|
954 |
(raw-value (org-trim |
|
955 |
(buffer-substring-no-properties title-start title-end))) |
|
956 |
(archivedp (member org-archive-tag tags)) |
|
957 |
(footnote-section-p (and org-footnote-section |
|
958 |
(string= org-footnote-section raw-value))) |
|
959 |
(standard-props (org-element--get-node-properties)) |
|
960 |
(time-props (org-element--get-time-properties)) |
|
961 |
(end (min (save-excursion (org-end-of-subtree t t)) limit)) |
|
962 |
(contents-begin (save-excursion |
|
963 |
(forward-line) |
|
964 |
(skip-chars-forward " \r\t\n" end) |
|
965 |
(and (/= (point) end) (line-beginning-position)))) |
|
966 |
(contents-end (and contents-begin |
|
967 |
(progn (goto-char end) |
|
968 |
(skip-chars-backward " \r\t\n") |
|
969 |
(line-beginning-position 2))))) |
|
970 |
(let ((headline |
|
971 |
(list 'headline |
|
972 |
(nconc |
|
973 |
(list :raw-value raw-value |
|
974 |
:begin begin |
|
975 |
:end end |
|
976 |
:pre-blank |
|
977 |
(if (not contents-begin) 0 |
|
978 |
(1- (count-lines begin contents-begin))) |
|
979 |
:contents-begin contents-begin |
|
980 |
:contents-end contents-end |
|
981 |
:level level |
|
982 |
:priority priority |
|
983 |
:tags tags |
|
984 |
:todo-keyword todo |
|
985 |
:todo-type todo-type |
|
986 |
:post-blank |
|
987 |
(if contents-end |
|
988 |
(count-lines contents-end end) |
|
989 |
(1- (count-lines begin end))) |
|
990 |
:footnote-section-p footnote-section-p |
|
991 |
:archivedp archivedp |
|
992 |
:commentedp commentedp |
|
993 |
:post-affiliated begin) |
|
994 |
time-props |
|
995 |
standard-props)))) |
|
996 |
(org-element-put-property |
|
997 |
headline :title |
|
998 |
(if raw-secondary-p raw-value |
|
999 |
(org-element--parse-objects |
|
1000 |
(progn (goto-char title-start) |
|
1001 |
(skip-chars-forward " \t") |
|
1002 |
(point)) |
|
1003 |
(progn (goto-char title-end) |
|
1004 |
(skip-chars-backward " \t") |
|
1005 |
(point)) |
|
1006 |
nil |
|
1007 |
(org-element-restriction 'headline) |
|
1008 |
headline))))))) |
|
1009 |
|
|
1010 |
(defun org-element-headline-interpreter (headline contents) |
|
1011 |
"Interpret HEADLINE element as Org syntax. |
|
1012 |
CONTENTS is the contents of the element." |
|
1013 |
(let* ((level (org-element-property :level headline)) |
|
1014 |
(todo (org-element-property :todo-keyword headline)) |
|
1015 |
(priority (org-element-property :priority headline)) |
|
1016 |
(title (org-element-interpret-data |
|
1017 |
(org-element-property :title headline))) |
|
1018 |
(tags (let ((tag-list (org-element-property :tags headline))) |
|
1019 |
(and tag-list |
|
1020 |
(format ":%s:" (mapconcat #'identity tag-list ":"))))) |
|
1021 |
(commentedp (org-element-property :commentedp headline)) |
|
1022 |
(pre-blank (or (org-element-property :pre-blank headline) 0)) |
|
1023 |
(heading |
|
1024 |
(concat (make-string (if org-odd-levels-only (1- (* level 2)) level) |
|
1025 |
?*) |
|
1026 |
(and todo (concat " " todo)) |
|
1027 |
(and commentedp (concat " " org-comment-string)) |
|
1028 |
(and priority (format " [#%c]" priority)) |
|
1029 |
" " |
|
1030 |
(if (and org-footnote-section |
|
1031 |
(org-element-property :footnote-section-p headline)) |
|
1032 |
org-footnote-section |
|
1033 |
title)))) |
|
1034 |
(concat |
|
1035 |
heading |
|
1036 |
;; Align tags. |
|
1037 |
(when tags |
|
1038 |
(cond |
|
1039 |
((zerop org-tags-column) (format " %s" tags)) |
|
1040 |
((< org-tags-column 0) |
|
1041 |
(concat |
|
1042 |
(make-string |
|
1043 |
(max (- (+ org-tags-column (length heading) (length tags))) 1) |
|
1044 |
?\s) |
|
1045 |
tags)) |
|
1046 |
(t |
|
1047 |
(concat |
|
1048 |
(make-string (max (- org-tags-column (length heading)) 1) ?\s) |
|
1049 |
tags)))) |
|
1050 |
(make-string (1+ pre-blank) ?\n) |
|
1051 |
contents))) |
|
1052 |
|
|
1053 |
|
|
1054 |
;;;; Inlinetask |
|
1055 |
|
|
1056 |
(defun org-element-inlinetask-parser (limit &optional raw-secondary-p) |
|
1057 |
"Parse an inline task. |
|
1058 |
|
|
1059 |
Return a list whose CAR is `inlinetask' and CDR is a plist |
|
1060 |
containing `:title', `:begin', `:end', `:pre-blank', |
|
1061 |
`:contents-begin' and `:contents-end', `:level', `:priority', |
|
1062 |
`:raw-value', `:tags', `:todo-keyword', `:todo-type', |
|
1063 |
`:scheduled', `:deadline', `:closed', `:post-blank' and |
|
1064 |
`:post-affiliated' keywords. |
|
1065 |
|
|
1066 |
The plist also contains any property set in the property drawer, |
|
1067 |
with its name in upper cases and colons added at the |
|
1068 |
beginning (e.g., `:CUSTOM_ID'). |
|
1069 |
|
|
1070 |
When optional argument RAW-SECONDARY-P is non-nil, inline-task's |
|
1071 |
title will not be parsed as a secondary string, but as a plain |
|
1072 |
string instead. |
|
1073 |
|
|
1074 |
Assume point is at beginning of the inline task." |
|
1075 |
(save-excursion |
|
1076 |
(let* ((begin (point)) |
|
1077 |
(level (prog1 (org-reduced-level (skip-chars-forward "*")) |
|
1078 |
(skip-chars-forward " \t"))) |
|
1079 |
(todo (and org-todo-regexp |
|
1080 |
(let (case-fold-search) (looking-at org-todo-regexp)) |
|
1081 |
(progn (goto-char (match-end 0)) |
|
1082 |
(skip-chars-forward " \t") |
|
1083 |
(match-string 0)))) |
|
1084 |
(todo-type (and todo |
|
1085 |
(if (member todo org-done-keywords) 'done 'todo))) |
|
1086 |
(priority (and (looking-at "\\[#.\\][ \t]*") |
|
1087 |
(progn (goto-char (match-end 0)) |
|
1088 |
(aref (match-string 0) 2)))) |
|
1089 |
(title-start (point)) |
|
1090 |
(tags (when (re-search-forward |
|
1091 |
"[ \t]+\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" |
|
1092 |
(line-end-position) |
|
1093 |
'move) |
|
1094 |
(goto-char (match-beginning 0)) |
|
1095 |
(org-split-string (match-string 1) ":"))) |
|
1096 |
(title-end (point)) |
|
1097 |
(raw-value (org-trim |
|
1098 |
(buffer-substring-no-properties title-start title-end))) |
|
1099 |
(task-end (save-excursion |
|
1100 |
(end-of-line) |
|
1101 |
(and (re-search-forward org-outline-regexp-bol limit t) |
|
1102 |
(looking-at-p "[ \t]*END[ \t]*$") |
|
1103 |
(line-beginning-position)))) |
|
1104 |
(standard-props (and task-end (org-element--get-node-properties))) |
|
1105 |
(time-props (and task-end (org-element--get-time-properties))) |
|
1106 |
(contents-begin (and task-end |
|
1107 |
(< (point) task-end) |
|
1108 |
(progn |
|
1109 |
(forward-line) |
|
1110 |
(skip-chars-forward " \t\n") |
|
1111 |
(line-beginning-position)))) |
|
1112 |
(contents-end (and contents-begin task-end)) |
|
1113 |
(end (progn (when task-end (goto-char task-end)) |
|
1114 |
(forward-line) |
|
1115 |
(skip-chars-forward " \r\t\n" limit) |
|
1116 |
(if (eobp) (point) (line-beginning-position)))) |
|
1117 |
(inlinetask |
|
1118 |
(list 'inlinetask |
|
1119 |
(nconc |
|
1120 |
(list :raw-value raw-value |
|
1121 |
:begin begin |
|
1122 |
:end end |
|
1123 |
:pre-blank |
|
1124 |
(if (not contents-begin) 0 |
|
1125 |
(1- (count-lines begin contents-begin))) |
|
1126 |
:contents-begin contents-begin |
|
1127 |
:contents-end contents-end |
|
1128 |
:level level |
|
1129 |
:priority priority |
|
1130 |
:tags tags |
|
1131 |
:todo-keyword todo |
|
1132 |
:todo-type todo-type |
|
1133 |
:post-blank (1- (count-lines (or task-end begin) end)) |
|
1134 |
:post-affiliated begin) |
|
1135 |
time-props |
|
1136 |
standard-props)))) |
|
1137 |
(org-element-put-property |
|
1138 |
inlinetask :title |
|
1139 |
(if raw-secondary-p raw-value |
|
1140 |
(org-element--parse-objects |
|
1141 |
(progn (goto-char title-start) |
|
1142 |
(skip-chars-forward " \t") |
|
1143 |
(point)) |
|
1144 |
(progn (goto-char title-end) |
|
1145 |
(skip-chars-backward " \t") |
|
1146 |
(point)) |
|
1147 |
nil |
|
1148 |
(org-element-restriction 'inlinetask) |
|
1149 |
inlinetask)))))) |
|
1150 |
|
|
1151 |
(defun org-element-inlinetask-interpreter (inlinetask contents) |
|
1152 |
"Interpret INLINETASK element as Org syntax. |
|
1153 |
CONTENTS is the contents of inlinetask." |
|
1154 |
(let* ((level (org-element-property :level inlinetask)) |
|
1155 |
(todo (org-element-property :todo-keyword inlinetask)) |
|
1156 |
(priority (org-element-property :priority inlinetask)) |
|
1157 |
(title (org-element-interpret-data |
|
1158 |
(org-element-property :title inlinetask))) |
|
1159 |
(tags (let ((tag-list (org-element-property :tags inlinetask))) |
|
1160 |
(and tag-list |
|
1161 |
(format ":%s:" (mapconcat 'identity tag-list ":"))))) |
|
1162 |
(task (concat (make-string level ?*) |
|
1163 |
(and todo (concat " " todo)) |
|
1164 |
(and priority (format " [#%c]" priority)) |
|
1165 |
(and title (concat " " title))))) |
|
1166 |
(concat task |
|
1167 |
;; Align tags. |
|
1168 |
(when tags |
|
1169 |
(cond |
|
1170 |
((zerop org-tags-column) (format " %s" tags)) |
|
1171 |
((< org-tags-column 0) |
|
1172 |
(concat |
|
1173 |
(make-string |
|
1174 |
(max (- (+ org-tags-column (length task) (length tags))) 1) |
|
1175 |
? ) |
|
1176 |
tags)) |
|
1177 |
(t |
|
1178 |
(concat |
|
1179 |
(make-string (max (- org-tags-column (length task)) 1) ? ) |
|
1180 |
tags)))) |
|
1181 |
;; Prefer degenerate inlinetasks when there are no |
|
1182 |
;; contents. |
|
1183 |
(when contents |
|
1184 |
(concat "\n" |
|
1185 |
contents |
|
1186 |
(make-string level ?*) " END"))))) |
|
1187 |
|
|
1188 |
|
|
1189 |
;;;; Item |
|
1190 |
|
|
1191 |
(defun org-element-item-parser (_ struct &optional raw-secondary-p) |
|
1192 |
"Parse an item. |
|
1193 |
|
|
1194 |
STRUCT is the structure of the plain list. |
|
1195 |
|
|
1196 |
Return a list whose CAR is `item' and CDR is a plist containing |
|
1197 |
`:bullet', `:begin', `:end', `:contents-begin', `:contents-end', |
|
1198 |
`:checkbox', `:counter', `:tag', `:structure', `:post-blank' and |
|
1199 |
`:post-affiliated' keywords. |
|
1200 |
|
|
1201 |
When optional argument RAW-SECONDARY-P is non-nil, item's tag, if |
|
1202 |
any, will not be parsed as a secondary string, but as a plain |
|
1203 |
string instead. |
|
1204 |
|
|
1205 |
Assume point is at the beginning of the item." |
|
1206 |
(save-excursion |
|
1207 |
(beginning-of-line) |
|
1208 |
(looking-at org-list-full-item-re) |
|
1209 |
(let* ((begin (point)) |
|
1210 |
(bullet (match-string-no-properties 1)) |
|
1211 |
(checkbox (let ((box (match-string 3))) |
|
1212 |
(cond ((equal "[ ]" box) 'off) |
|
1213 |
((equal "[X]" box) 'on) |
|
1214 |
((equal "[-]" box) 'trans)))) |
|
1215 |
(counter (let ((c (match-string 2))) |
|
1216 |
(save-match-data |
|
1217 |
(cond |
|
1218 |
((not c) nil) |
|
1219 |
((string-match "[A-Za-z]" c) |
|
1220 |
(- (string-to-char (upcase (match-string 0 c))) |
|
1221 |
64)) |
|
1222 |
((string-match "[0-9]+" c) |
|
1223 |
(string-to-number (match-string 0 c))))))) |
|
1224 |
(end (progn (goto-char (nth 6 (assq (point) struct))) |
|
1225 |
(if (bolp) (point) (line-beginning-position 2)))) |
|
1226 |
(contents-begin |
|
1227 |
(progn (goto-char |
|
1228 |
;; Ignore tags in un-ordered lists: they are just |
|
1229 |
;; a part of item's body. |
|
1230 |
(if (and (match-beginning 4) |
|
1231 |
(save-match-data (string-match "[.)]" bullet))) |
|
1232 |
(match-beginning 4) |
|
1233 |
(match-end 0))) |
|
1234 |
(skip-chars-forward " \r\t\n" end) |
|
1235 |
(cond ((= (point) end) nil) |
|
1236 |
;; If first line isn't empty, contents really |
|
1237 |
;; start at the text after item's meta-data. |
|
1238 |
((= (line-beginning-position) begin) (point)) |
|
1239 |
(t (line-beginning-position))))) |
|
1240 |
(contents-end (and contents-begin |
|
1241 |
(progn (goto-char end) |
|
1242 |
(skip-chars-backward " \r\t\n") |
|
1243 |
(line-beginning-position 2)))) |
|
1244 |
(item |
|
1245 |
(list 'item |
|
1246 |
(list :bullet bullet |
|
1247 |
:begin begin |
|
1248 |
:end end |
|
1249 |
:contents-begin contents-begin |
|
1250 |
:contents-end contents-end |
|
1251 |
:checkbox checkbox |
|
1252 |
:counter counter |
|
1253 |
:structure struct |
|
1254 |
:post-blank (count-lines (or contents-end begin) end) |
|
1255 |
:post-affiliated begin)))) |
|
1256 |
(org-element-put-property |
|
1257 |
item :tag |
|
1258 |
(let ((raw (org-list-get-tag begin struct))) |
|
1259 |
(when raw |
|
1260 |
(if raw-secondary-p raw |
|
1261 |
(org-element--parse-objects |
|
1262 |
(match-beginning 4) (match-end 4) nil |
|
1263 |
(org-element-restriction 'item) |
|
1264 |
item)))))))) |
|
1265 |
|
|
1266 |
(defun org-element-item-interpreter (item contents) |
|
1267 |
"Interpret ITEM element as Org syntax. |
|
1268 |
CONTENTS is the contents of the element." |
|
1269 |
(let* ((bullet (let ((bullet (org-element-property :bullet item))) |
|
1270 |
(org-list-bullet-string |
|
1271 |
(cond ((not (string-match "[0-9a-zA-Z]" bullet)) "- ") |
|
1272 |
((eq org-plain-list-ordered-item-terminator ?\)) "1)") |
|
1273 |
(t "1."))))) |
|
1274 |
(checkbox (org-element-property :checkbox item)) |
|
1275 |
(counter (org-element-property :counter item)) |
|
1276 |
(tag (let ((tag (org-element-property :tag item))) |
|
1277 |
(and tag (org-element-interpret-data tag)))) |
|
1278 |
;; Compute indentation. |
|
1279 |
(ind (make-string (length bullet) 32)) |
|
1280 |
(item-starts-with-par-p |
|
1281 |
(eq (org-element-type (car (org-element-contents item))) |
|
1282 |
'paragraph))) |
|
1283 |
;; Indent contents. |
|
1284 |
(concat |
|
1285 |
bullet |
|
1286 |
(and counter (format "[@%d] " counter)) |
|
1287 |
(pcase checkbox |
|
1288 |
(`on "[X] ") |
|
1289 |
(`off "[ ] ") |
|
1290 |
(`trans "[-] ") |
|
1291 |
(_ nil)) |
|
1292 |
(and tag (format "%s :: " tag)) |
|
1293 |
(when contents |
|
1294 |
(let ((contents (replace-regexp-in-string |
|
1295 |
"\\(^\\)[ \t]*\\S-" ind contents nil nil 1))) |
|
1296 |
(if item-starts-with-par-p (org-trim contents) |
|
1297 |
(concat "\n" contents))))))) |
|
1298 |
|
|
1299 |
|
|
1300 |
;;;; Plain List |
|
1301 |
|
|
1302 |
(defun org-element--list-struct (limit) |
|
1303 |
;; Return structure of list at point. Internal function. See |
|
1304 |
;; `org-list-struct' for details. |
|
1305 |
(let ((case-fold-search t) |
|
1306 |
(top-ind limit) |
|
1307 |
(item-re (org-item-re)) |
|
1308 |
(inlinetask-re (and (featurep 'org-inlinetask) "^\\*+ ")) |
|
1309 |
items struct) |
|
1310 |
(save-excursion |
|
1311 |
(catch :exit |
|
1312 |
(while t |
|
1313 |
(cond |
|
1314 |
;; At limit: end all items. |
|
1315 |
((>= (point) limit) |
|
1316 |
(let ((end (progn (skip-chars-backward " \r\t\n") |
|
1317 |
(line-beginning-position 2)))) |
|
1318 |
(dolist (item items) (setcar (nthcdr 6 item) end))) |
|
1319 |
(throw :exit (sort (nconc items struct) #'car-less-than-car))) |
|
1320 |
;; At list end: end all items. |
|
1321 |
((looking-at org-list-end-re) |
|
1322 |
(dolist (item items) (setcar (nthcdr 6 item) (point))) |
|
1323 |
(throw :exit (sort (nconc items struct) #'car-less-than-car))) |
|
1324 |
;; At a new item: end previous sibling. |
|
1325 |
((looking-at item-re) |
|
1326 |
(let ((ind (save-excursion (skip-chars-forward " \t") |
|
1327 |
(current-column)))) |
|
1328 |
(setq top-ind (min top-ind ind)) |
|
1329 |
(while (and items (<= ind (nth 1 (car items)))) |
|
1330 |
(let ((item (pop items))) |
|
1331 |
(setcar (nthcdr 6 item) (point)) |
|
1332 |
(push item struct))) |
|
1333 |
(push (progn (looking-at org-list-full-item-re) |
|
1334 |
(let ((bullet (match-string-no-properties 1))) |
|
1335 |
(list (point) |
|
1336 |
ind |
|
1337 |
bullet |
|
1338 |
(match-string-no-properties 2) ; counter |
|
1339 |
(match-string-no-properties 3) ; checkbox |
|
1340 |
;; Description tag. |
|
1341 |
(and (save-match-data |
|
1342 |
(string-match "[-+*]" bullet)) |
|
1343 |
(match-string-no-properties 4)) |
|
1344 |
;; Ending position, unknown so far. |
|
1345 |
nil))) |
|
1346 |
items)) |
|
1347 |
(forward-line)) |
|
1348 |
;; Skip empty lines. |
|
1349 |
((looking-at "^[ \t]*$") (forward-line)) |
|
1350 |
;; Skip inline tasks and blank lines along the way. |
|
1351 |
((and inlinetask-re (looking-at inlinetask-re)) |
|
1352 |
(forward-line) |
|
1353 |
(let ((origin (point))) |
|
1354 |
(when (re-search-forward inlinetask-re limit t) |
|
1355 |
(if (looking-at-p "END[ \t]*$") (forward-line) |
|
1356 |
(goto-char origin))))) |
|
1357 |
;; At some text line. Check if it ends any previous item. |
|
1358 |
(t |
|
1359 |
(let ((ind (save-excursion |
|
1360 |
(skip-chars-forward " \t") |
|
1361 |
(current-column))) |
|
1362 |
(end (save-excursion |
|
1363 |
(skip-chars-backward " \r\t\n") |
|
1364 |
(line-beginning-position 2)))) |
|
1365 |
(while (<= ind (nth 1 (car items))) |
|
1366 |
(let ((item (pop items))) |
|
1367 |
(setcar (nthcdr 6 item) end) |
|
1368 |
(push item struct) |
|
1369 |
(unless items |
|
1370 |
(throw :exit (sort struct #'car-less-than-car)))))) |
|
1371 |
;; Skip blocks (any type) and drawers contents. |
|
1372 |
(cond |
|
1373 |
((and (looking-at "[ \t]*#\\+BEGIN\\(:\\|_\\S-+\\)") |
|
1374 |
(re-search-forward |
|
1375 |
(format "^[ \t]*#\\+END%s[ \t]*$" (match-string 1)) |
|
1376 |
limit t))) |
|
1377 |
((and (looking-at org-drawer-regexp) |
|
1378 |
(re-search-forward "^[ \t]*:END:[ \t]*$" limit t)))) |
|
1379 |
(forward-line)))))))) |
|
1380 |
|
|
1381 |
(defun org-element-plain-list-parser (limit affiliated structure) |
|
1382 |
"Parse a plain list. |
|
1383 |
|
|
1384 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1385 |
the buffer position at the beginning of the first affiliated |
|
1386 |
keyword and CDR is a plist of affiliated keywords along with |
|
1387 |
their value. STRUCTURE is the structure of the plain list being |
|
1388 |
parsed. |
|
1389 |
|
|
1390 |
Return a list whose CAR is `plain-list' and CDR is a plist |
|
1391 |
containing `:type', `:begin', `:end', `:contents-begin' and |
|
1392 |
`:contents-end', `:structure', `:post-blank' and |
|
1393 |
`:post-affiliated' keywords. |
|
1394 |
|
|
1395 |
Assume point is at the beginning of the list." |
|
1396 |
(save-excursion |
|
1397 |
(let* ((struct (or structure (org-element--list-struct limit))) |
|
1398 |
(type (cond ((looking-at-p "[ \t]*[A-Za-z0-9]") 'ordered) |
|
1399 |
((nth 5 (assq (point) struct)) 'descriptive) |
|
1400 |
(t 'unordered))) |
|
1401 |
(contents-begin (point)) |
|
1402 |
(begin (car affiliated)) |
|
1403 |
(contents-end (let* ((item (assq contents-begin struct)) |
|
1404 |
(ind (nth 1 item)) |
|
1405 |
(pos (nth 6 item))) |
|
1406 |
(while (and (setq item (assq pos struct)) |
|
1407 |
(= (nth 1 item) ind)) |
|
1408 |
(setq pos (nth 6 item))) |
|
1409 |
pos)) |
|
1410 |
(end (progn (goto-char contents-end) |
|
1411 |
(skip-chars-forward " \r\t\n" limit) |
|
1412 |
(if (= (point) limit) limit (line-beginning-position))))) |
|
1413 |
;; Return value. |
|
1414 |
(list 'plain-list |
|
1415 |
(nconc |
|
1416 |
(list :type type |
|
1417 |
:begin begin |
|
1418 |
:end end |
|
1419 |
:contents-begin contents-begin |
|
1420 |
:contents-end contents-end |
|
1421 |
:structure struct |
|
1422 |
:post-blank (count-lines contents-end end) |
|
1423 |
:post-affiliated contents-begin) |
|
1424 |
(cdr affiliated)))))) |
|
1425 |
|
|
1426 |
(defun org-element-plain-list-interpreter (_ contents) |
|
1427 |
"Interpret plain-list element as Org syntax. |
|
1428 |
CONTENTS is the contents of the element." |
|
1429 |
(with-temp-buffer |
|
1430 |
(insert contents) |
|
1431 |
(goto-char (point-min)) |
|
1432 |
(org-list-repair) |
|
1433 |
(buffer-string))) |
|
1434 |
|
|
1435 |
|
|
1436 |
;;;; Property Drawer |
|
1437 |
|
|
1438 |
(defun org-element-property-drawer-parser (limit) |
|
1439 |
"Parse a property drawer. |
|
1440 |
|
|
1441 |
LIMIT bounds the search. |
|
1442 |
|
|
1443 |
Return a list whose car is `property-drawer' and cdr is a plist |
|
1444 |
containing `:begin', `:end', `:contents-begin', `:contents-end', |
|
1445 |
`:post-blank' and `:post-affiliated' keywords. |
|
1446 |
|
|
1447 |
Assume point is at the beginning of the property drawer." |
|
1448 |
(save-excursion |
|
1449 |
(let ((case-fold-search t) |
|
1450 |
(begin (point)) |
|
1451 |
(contents-begin (line-beginning-position 2))) |
|
1452 |
(re-search-forward "^[ \t]*:END:[ \t]*$" limit t) |
|
1453 |
(let ((contents-end (and (> (match-beginning 0) contents-begin) |
|
1454 |
(match-beginning 0))) |
|
1455 |
(before-blank (progn (forward-line) (point))) |
|
1456 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
1457 |
(if (eobp) (point) (line-beginning-position))))) |
|
1458 |
(list 'property-drawer |
|
1459 |
(list :begin begin |
|
1460 |
:end end |
|
1461 |
:contents-begin (and contents-end contents-begin) |
|
1462 |
:contents-end contents-end |
|
1463 |
:post-blank (count-lines before-blank end) |
|
1464 |
:post-affiliated begin)))))) |
|
1465 |
|
|
1466 |
(defun org-element-property-drawer-interpreter (_ contents) |
|
1467 |
"Interpret property-drawer element as Org syntax. |
|
1468 |
CONTENTS is the properties within the drawer." |
|
1469 |
(format ":PROPERTIES:\n%s:END:" contents)) |
|
1470 |
|
|
1471 |
|
|
1472 |
;;;; Quote Block |
|
1473 |
|
|
1474 |
(defun org-element-quote-block-parser (limit affiliated) |
|
1475 |
"Parse a quote block. |
|
1476 |
|
|
1477 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1478 |
the buffer position at the beginning of the first affiliated |
|
1479 |
keyword and CDR is a plist of affiliated keywords along with |
|
1480 |
their value. |
|
1481 |
|
|
1482 |
Return a list whose CAR is `quote-block' and CDR is a plist |
|
1483 |
containing `:begin', `:end', `:contents-begin', `:contents-end', |
|
1484 |
`:post-blank' and `:post-affiliated' keywords. |
|
1485 |
|
|
1486 |
Assume point is at the beginning of the block." |
|
1487 |
(let ((case-fold-search t)) |
|
1488 |
(if (not (save-excursion |
|
1489 |
(re-search-forward "^[ \t]*#\\+END_QUOTE[ \t]*$" limit t))) |
|
1490 |
;; Incomplete block: parse it as a paragraph. |
|
1491 |
(org-element-paragraph-parser limit affiliated) |
|
1492 |
(let ((block-end-line (match-beginning 0))) |
|
1493 |
(save-excursion |
|
1494 |
(let* ((begin (car affiliated)) |
|
1495 |
(post-affiliated (point)) |
|
1496 |
;; Empty blocks have no contents. |
|
1497 |
(contents-begin (progn (forward-line) |
|
1498 |
(and (< (point) block-end-line) |
|
1499 |
(point)))) |
|
1500 |
(contents-end (and contents-begin block-end-line)) |
|
1501 |
(pos-before-blank (progn (goto-char block-end-line) |
|
1502 |
(forward-line) |
|
1503 |
(point))) |
|
1504 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
1505 |
(if (eobp) (point) (line-beginning-position))))) |
|
1506 |
(list 'quote-block |
|
1507 |
(nconc |
|
1508 |
(list :begin begin |
|
1509 |
:end end |
|
1510 |
:contents-begin contents-begin |
|
1511 |
:contents-end contents-end |
|
1512 |
:post-blank (count-lines pos-before-blank end) |
|
1513 |
:post-affiliated post-affiliated) |
|
1514 |
(cdr affiliated))))))))) |
|
1515 |
|
|
1516 |
(defun org-element-quote-block-interpreter (_ contents) |
|
1517 |
"Interpret quote-block element as Org syntax. |
|
1518 |
CONTENTS is the contents of the element." |
|
1519 |
(format "#+BEGIN_QUOTE\n%s#+END_QUOTE" contents)) |
|
1520 |
|
|
1521 |
|
|
1522 |
;;;; Section |
|
1523 |
|
|
1524 |
(defun org-element-section-parser (_) |
|
1525 |
"Parse a section. |
|
1526 |
|
|
1527 |
Return a list whose CAR is `section' and CDR is a plist |
|
1528 |
containing `:begin', `:end', `:contents-begin', `contents-end', |
|
1529 |
`:post-blank' and `:post-affiliated' keywords." |
|
1530 |
(save-excursion |
|
1531 |
;; Beginning of section is the beginning of the first non-blank |
|
1532 |
;; line after previous headline. |
|
1533 |
(let ((begin (point)) |
|
1534 |
(end (progn (org-with-limited-levels (outline-next-heading)) |
|
1535 |
(point))) |
|
1536 |
(pos-before-blank (progn (skip-chars-backward " \r\t\n") |
|
1537 |
(line-beginning-position 2)))) |
|
1538 |
(list 'section |
|
1539 |
(list :begin begin |
|
1540 |
:end end |
|
1541 |
:contents-begin begin |
|
1542 |
:contents-end pos-before-blank |
|
1543 |
:post-blank (count-lines pos-before-blank end) |
|
1544 |
:post-affiliated begin))))) |
|
1545 |
|
|
1546 |
(defun org-element-section-interpreter (_ contents) |
|
1547 |
"Interpret section element as Org syntax. |
|
1548 |
CONTENTS is the contents of the element." |
|
1549 |
contents) |
|
1550 |
|
|
1551 |
|
|
1552 |
;;;; Special Block |
|
1553 |
|
|
1554 |
(defun org-element-special-block-parser (limit affiliated) |
|
1555 |
"Parse a special block. |
|
1556 |
|
|
1557 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1558 |
the buffer position at the beginning of the first affiliated |
|
1559 |
keyword and CDR is a plist of affiliated keywords along with |
|
1560 |
their value. |
|
1561 |
|
|
1562 |
Return a list whose CAR is `special-block' and CDR is a plist |
|
1563 |
containing `:type', `:begin', `:end', `:contents-begin', |
|
1564 |
`:contents-end', `:post-blank' and `:post-affiliated' keywords. |
|
1565 |
|
|
1566 |
Assume point is at the beginning of the block." |
|
1567 |
(let* ((case-fold-search t) |
|
1568 |
(type (progn (looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") |
|
1569 |
(match-string-no-properties 1)))) |
|
1570 |
(if (not (save-excursion |
|
1571 |
(re-search-forward |
|
1572 |
(format "^[ \t]*#\\+END_%s[ \t]*$" (regexp-quote type)) |
|
1573 |
limit t))) |
|
1574 |
;; Incomplete block: parse it as a paragraph. |
|
1575 |
(org-element-paragraph-parser limit affiliated) |
|
1576 |
(let ((block-end-line (match-beginning 0))) |
|
1577 |
(save-excursion |
|
1578 |
(let* ((begin (car affiliated)) |
|
1579 |
(post-affiliated (point)) |
|
1580 |
;; Empty blocks have no contents. |
|
1581 |
(contents-begin (progn (forward-line) |
|
1582 |
(and (< (point) block-end-line) |
|
1583 |
(point)))) |
|
1584 |
(contents-end (and contents-begin block-end-line)) |
|
1585 |
(pos-before-blank (progn (goto-char block-end-line) |
|
1586 |
(forward-line) |
|
1587 |
(point))) |
|
1588 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
1589 |
(if (eobp) (point) (line-beginning-position))))) |
|
1590 |
(list 'special-block |
|
1591 |
(nconc |
|
1592 |
(list :type type |
|
1593 |
:begin begin |
|
1594 |
:end end |
|
1595 |
:contents-begin contents-begin |
|
1596 |
:contents-end contents-end |
|
1597 |
:post-blank (count-lines pos-before-blank end) |
|
1598 |
:post-affiliated post-affiliated) |
|
1599 |
(cdr affiliated))))))))) |
|
1600 |
|
|
1601 |
(defun org-element-special-block-interpreter (special-block contents) |
|
1602 |
"Interpret SPECIAL-BLOCK element as Org syntax. |
|
1603 |
CONTENTS is the contents of the element." |
|
1604 |
(let ((block-type (org-element-property :type special-block))) |
|
1605 |
(format "#+BEGIN_%s\n%s#+END_%s" block-type contents block-type))) |
|
1606 |
|
|
1607 |
|
|
1608 |
|
|
1609 |
;;; Elements |
|
1610 |
;; |
|
1611 |
;; For each element, a parser and an interpreter are also defined. |
|
1612 |
;; Both follow the same naming convention used for greater elements. |
|
1613 |
;; |
|
1614 |
;; Also, as for greater elements, adding a new element type is done |
|
1615 |
;; through the following steps: implement a parser and an interpreter, |
|
1616 |
;; tweak `org-element--current-element' so that it recognizes the new |
|
1617 |
;; type and add that new type to `org-element-all-elements'. |
|
1618 |
|
|
1619 |
|
|
1620 |
;;;; Babel Call |
|
1621 |
|
|
1622 |
(defun org-element-babel-call-parser (limit affiliated) |
|
1623 |
"Parse a babel call. |
|
1624 |
|
|
1625 |
LIMIT bounds the search. AFFILIATED is a list of which car is |
|
1626 |
the buffer position at the beginning of the first affiliated |
|
1627 |
keyword and cdr is a plist of affiliated keywords along with |
|
1628 |
their value. |
|
1629 |
|
|
1630 |
Return a list whose car is `babel-call' and cdr is a plist |
|
1631 |
containing `:call', `:inside-header', `:arguments', |
|
1632 |
`:end-header', `:begin', `:end', `:value', `:post-blank' and |
|
1633 |
`:post-affiliated' as keywords." |
|
1634 |
(save-excursion |
|
1635 |
(let* ((begin (car affiliated)) |
|
1636 |
(post-affiliated (point)) |
|
1637 |
(before-blank (line-beginning-position 2)) |
|
1638 |
(value (progn (search-forward ":" before-blank t) |
|
1639 |
(skip-chars-forward " \t") |
|
1640 |
(org-trim |
|
1641 |
(buffer-substring-no-properties |
|
1642 |
(point) (line-end-position))))) |
|
1643 |
(call |
|
1644 |
(or (org-string-nw-p |
|
1645 |
(buffer-substring-no-properties |
|
1646 |
(point) (progn (skip-chars-forward "^[]()" before-blank) |
|
1647 |
(point)))))) |
|
1648 |
(inside-header (org-element--parse-paired-brackets ?\[)) |
|
1649 |
(arguments (org-string-nw-p |
|
1650 |
(org-element--parse-paired-brackets ?\())) |
|
1651 |
(end-header |
|
1652 |
(org-string-nw-p |
|
1653 |
(org-trim |
|
1654 |
(buffer-substring-no-properties (point) (line-end-position))))) |
|
1655 |
(end (progn (forward-line) |
|
1656 |
(skip-chars-forward " \r\t\n" limit) |
|
1657 |
(if (eobp) (point) (line-beginning-position))))) |
|
1658 |
(list 'babel-call |
|
1659 |
(nconc |
|
1660 |
(list :call call |
|
1661 |
:inside-header inside-header |
|
1662 |
:arguments arguments |
|
1663 |
:end-header end-header |
|
1664 |
:begin begin |
|
1665 |
:end end |
|
1666 |
:value value |
|
1667 |
:post-blank (count-lines before-blank end) |
|
1668 |
:post-affiliated post-affiliated) |
|
1669 |
(cdr affiliated)))))) |
|
1670 |
|
|
1671 |
(defun org-element-babel-call-interpreter (babel-call _) |
|
1672 |
"Interpret BABEL-CALL element as Org syntax." |
|
1673 |
(concat "#+CALL: " |
|
1674 |
(org-element-property :call babel-call) |
|
1675 |
(let ((h (org-element-property :inside-header babel-call))) |
|
1676 |
(and h (format "[%s]" h))) |
|
1677 |
(concat "(" (org-element-property :arguments babel-call) ")") |
|
1678 |
(let ((h (org-element-property :end-header babel-call))) |
|
1679 |
(and h (concat " " h))))) |
|
1680 |
|
|
1681 |
|
|
1682 |
;;;; Clock |
|
1683 |
|
|
1684 |
(defun org-element-clock-parser (limit) |
|
1685 |
"Parse a clock. |
|
1686 |
|
|
1687 |
LIMIT bounds the search. |
|
1688 |
|
|
1689 |
Return a list whose CAR is `clock' and CDR is a plist containing |
|
1690 |
`:status', `:value', `:time', `:begin', `:end', `:post-blank' and |
|
1691 |
`:post-affiliated' as keywords." |
|
1692 |
(save-excursion |
|
1693 |
(let* ((case-fold-search nil) |
|
1694 |
(begin (point)) |
|
1695 |
(value (progn (search-forward org-clock-string (line-end-position) t) |
|
1696 |
(skip-chars-forward " \t") |
|
1697 |
(org-element-timestamp-parser))) |
|
1698 |
(duration (and (search-forward " => " (line-end-position) t) |
|
1699 |
(progn (skip-chars-forward " \t") |
|
1700 |
(looking-at "\\(\\S-+\\)[ \t]*$")) |
|
1701 |
(match-string-no-properties 1))) |
|
1702 |
(status (if duration 'closed 'running)) |
|
1703 |
(post-blank (let ((before-blank (progn (forward-line) (point)))) |
|
1704 |
(skip-chars-forward " \r\t\n" limit) |
|
1705 |
(skip-chars-backward " \t") |
|
1706 |
(unless (bolp) (end-of-line)) |
|
1707 |
(count-lines before-blank (point)))) |
|
1708 |
(end (point))) |
|
1709 |
(list 'clock |
|
1710 |
(list :status status |
|
1711 |
:value value |
|
1712 |
:duration duration |
|
1713 |
:begin begin |
|
1714 |
:end end |
|
1715 |
:post-blank post-blank |
|
1716 |
:post-affiliated begin))))) |
|
1717 |
|
|
1718 |
(defun org-element-clock-interpreter (clock _) |
|
1719 |
"Interpret CLOCK element as Org syntax." |
|
1720 |
(concat org-clock-string " " |
|
1721 |
(org-element-timestamp-interpreter |
|
1722 |
(org-element-property :value clock) nil) |
|
1723 |
(let ((duration (org-element-property :duration clock))) |
|
1724 |
(and duration |
|
1725 |
(concat " => " |
|
1726 |
(apply 'format |
|
1727 |
"%2s:%02s" |
|
1728 |
(org-split-string duration ":"))))))) |
|
1729 |
|
|
1730 |
|
|
1731 |
;;;; Comment |
|
1732 |
|
|
1733 |
(defun org-element-comment-parser (limit affiliated) |
|
1734 |
"Parse a comment. |
|
1735 |
|
|
1736 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1737 |
the buffer position at the beginning of the first affiliated |
|
1738 |
keyword and CDR is a plist of affiliated keywords along with |
|
1739 |
their value. |
|
1740 |
|
|
1741 |
Return a list whose CAR is `comment' and CDR is a plist |
|
1742 |
containing `:begin', `:end', `:value', `:post-blank', |
|
1743 |
`:post-affiliated' keywords. |
|
1744 |
|
|
1745 |
Assume point is at comment beginning." |
|
1746 |
(save-excursion |
|
1747 |
(let* ((begin (car affiliated)) |
|
1748 |
(post-affiliated (point)) |
|
1749 |
(value (prog2 (looking-at "[ \t]*# ?") |
|
1750 |
(buffer-substring-no-properties |
|
1751 |
(match-end 0) (line-end-position)) |
|
1752 |
(forward-line))) |
|
1753 |
(com-end |
|
1754 |
;; Get comments ending. |
|
1755 |
(progn |
|
1756 |
(while (and (< (point) limit) (looking-at "[ \t]*#\\( \\|$\\)")) |
|
1757 |
;; Accumulate lines without leading hash and first |
|
1758 |
;; whitespace. |
|
1759 |
(setq value |
|
1760 |
(concat value |
|
1761 |
"\n" |
|
1762 |
(buffer-substring-no-properties |
|
1763 |
(match-end 0) (line-end-position)))) |
|
1764 |
(forward-line)) |
|
1765 |
(point))) |
|
1766 |
(end (progn (goto-char com-end) |
|
1767 |
(skip-chars-forward " \r\t\n" limit) |
|
1768 |
(if (eobp) (point) (line-beginning-position))))) |
|
1769 |
(list 'comment |
|
1770 |
(nconc |
|
1771 |
(list :begin begin |
|
1772 |
:end end |
|
1773 |
:value value |
|
1774 |
:post-blank (count-lines com-end end) |
|
1775 |
:post-affiliated post-affiliated) |
|
1776 |
(cdr affiliated)))))) |
|
1777 |
|
|
1778 |
(defun org-element-comment-interpreter (comment _) |
|
1779 |
"Interpret COMMENT element as Org syntax. |
|
1780 |
CONTENTS is nil." |
|
1781 |
(replace-regexp-in-string "^" "# " (org-element-property :value comment))) |
|
1782 |
|
|
1783 |
|
|
1784 |
;;;; Comment Block |
|
1785 |
|
|
1786 |
(defun org-element-comment-block-parser (limit affiliated) |
|
1787 |
"Parse an export block. |
|
1788 |
|
|
1789 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1790 |
the buffer position at the beginning of the first affiliated |
|
1791 |
keyword and CDR is a plist of affiliated keywords along with |
|
1792 |
their value. |
|
1793 |
|
|
1794 |
Return a list whose CAR is `comment-block' and CDR is a plist |
|
1795 |
containing `:begin', `:end', `:value', `:post-blank' and |
|
1796 |
`:post-affiliated' keywords. |
|
1797 |
|
|
1798 |
Assume point is at comment block beginning." |
|
1799 |
(let ((case-fold-search t)) |
|
1800 |
(if (not (save-excursion |
|
1801 |
(re-search-forward "^[ \t]*#\\+END_COMMENT[ \t]*$" limit t))) |
|
1802 |
;; Incomplete block: parse it as a paragraph. |
|
1803 |
(org-element-paragraph-parser limit affiliated) |
|
1804 |
(let ((contents-end (match-beginning 0))) |
|
1805 |
(save-excursion |
|
1806 |
(let* ((begin (car affiliated)) |
|
1807 |
(post-affiliated (point)) |
|
1808 |
(contents-begin (progn (forward-line) (point))) |
|
1809 |
(pos-before-blank (progn (goto-char contents-end) |
|
1810 |
(forward-line) |
|
1811 |
(point))) |
|
1812 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
1813 |
(if (eobp) (point) (line-beginning-position)))) |
|
1814 |
(value (buffer-substring-no-properties |
|
1815 |
contents-begin contents-end))) |
|
1816 |
(list 'comment-block |
|
1817 |
(nconc |
|
1818 |
(list :begin begin |
|
1819 |
:end end |
|
1820 |
:value value |
|
1821 |
:post-blank (count-lines pos-before-blank end) |
|
1822 |
:post-affiliated post-affiliated) |
|
1823 |
(cdr affiliated))))))))) |
|
1824 |
|
|
1825 |
(defun org-element-comment-block-interpreter (comment-block _) |
|
1826 |
"Interpret COMMENT-BLOCK element as Org syntax." |
|
1827 |
(format "#+BEGIN_COMMENT\n%s#+END_COMMENT" |
|
1828 |
(org-element-normalize-string |
|
1829 |
(org-remove-indentation |
|
1830 |
(org-element-property :value comment-block))))) |
|
1831 |
|
|
1832 |
|
|
1833 |
;;;; Diary Sexp |
|
1834 |
|
|
1835 |
(defun org-element-diary-sexp-parser (limit affiliated) |
|
1836 |
"Parse a diary sexp. |
|
1837 |
|
|
1838 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1839 |
the buffer position at the beginning of the first affiliated |
|
1840 |
keyword and CDR is a plist of affiliated keywords along with |
|
1841 |
their value. |
|
1842 |
|
|
1843 |
Return a list whose CAR is `diary-sexp' and CDR is a plist |
|
1844 |
containing `:begin', `:end', `:value', `:post-blank' and |
|
1845 |
`:post-affiliated' keywords." |
|
1846 |
(save-excursion |
|
1847 |
(let ((begin (car affiliated)) |
|
1848 |
(post-affiliated (point)) |
|
1849 |
(value (progn (looking-at "\\(%%(.*\\)[ \t]*$") |
|
1850 |
(match-string-no-properties 1))) |
|
1851 |
(pos-before-blank (progn (forward-line) (point))) |
|
1852 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
1853 |
(if (eobp) (point) (line-beginning-position))))) |
|
1854 |
(list 'diary-sexp |
|
1855 |
(nconc |
|
1856 |
(list :value value |
|
1857 |
:begin begin |
|
1858 |
:end end |
|
1859 |
:post-blank (count-lines pos-before-blank end) |
|
1860 |
:post-affiliated post-affiliated) |
|
1861 |
(cdr affiliated)))))) |
|
1862 |
|
|
1863 |
(defun org-element-diary-sexp-interpreter (diary-sexp _) |
|
1864 |
"Interpret DIARY-SEXP as Org syntax." |
|
1865 |
(org-element-property :value diary-sexp)) |
|
1866 |
|
|
1867 |
|
|
1868 |
;;;; Example Block |
|
1869 |
|
|
1870 |
(defun org-element-example-block-parser (limit affiliated) |
|
1871 |
"Parse an example block. |
|
1872 |
|
|
1873 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1874 |
the buffer position at the beginning of the first affiliated |
|
1875 |
keyword and CDR is a plist of affiliated keywords along with |
|
1876 |
their value. |
|
1877 |
|
|
1878 |
Return a list whose CAR is `example-block' and CDR is a plist |
|
1879 |
containing `:begin', `:end', `:number-lines', `:preserve-indent', |
|
1880 |
`:retain-labels', `:use-labels', `:label-fmt', `:switches', |
|
1881 |
`:value', `:post-blank' and `:post-affiliated' keywords." |
|
1882 |
(let ((case-fold-search t)) |
|
1883 |
(if (not (save-excursion |
|
1884 |
(re-search-forward "^[ \t]*#\\+END_EXAMPLE[ \t]*$" limit t))) |
|
1885 |
;; Incomplete block: parse it as a paragraph. |
|
1886 |
(org-element-paragraph-parser limit affiliated) |
|
1887 |
(let ((contents-end (match-beginning 0))) |
|
1888 |
(save-excursion |
|
1889 |
(let* ((switches |
|
1890 |
(progn |
|
1891 |
(looking-at "^[ \t]*#\\+BEGIN_EXAMPLE\\(?: +\\(.*\\)\\)?") |
|
1892 |
(match-string-no-properties 1))) |
|
1893 |
;; Switches analysis. |
|
1894 |
(number-lines |
|
1895 |
(and switches |
|
1896 |
(string-match "\\([-+]\\)n\\(?: *\\([0-9]+\\)\\)?\\>" |
|
1897 |
switches) |
|
1898 |
(cons |
|
1899 |
(if (equal (match-string 1 switches) "-") |
|
1900 |
'new |
|
1901 |
'continued) |
|
1902 |
(if (not (match-end 2)) 0 |
|
1903 |
;; Subtract 1 to give number of lines before |
|
1904 |
;; first line. |
|
1905 |
(1- (string-to-number (match-string 2 switches))))))) |
|
1906 |
(preserve-indent |
|
1907 |
(and switches (string-match "-i\\>" switches))) |
|
1908 |
;; Should labels be retained in (or stripped from) example |
|
1909 |
;; blocks? |
|
1910 |
(retain-labels |
|
1911 |
(or (not switches) |
|
1912 |
(not (string-match "-r\\>" switches)) |
|
1913 |
(and number-lines (string-match "-k\\>" switches)))) |
|
1914 |
;; What should code-references use - labels or |
|
1915 |
;; line-numbers? |
|
1916 |
(use-labels |
|
1917 |
(or (not switches) |
|
1918 |
(and retain-labels |
|
1919 |
(not (string-match "-k\\>" switches))))) |
|
1920 |
(label-fmt |
|
1921 |
(and switches |
|
1922 |
(string-match "-l +\"\\([^\"\n]+\\)\"" switches) |
|
1923 |
(match-string 1 switches))) |
|
1924 |
;; Standard block parsing. |
|
1925 |
(begin (car affiliated)) |
|
1926 |
(post-affiliated (point)) |
|
1927 |
(contents-begin (line-beginning-position 2)) |
|
1928 |
(value (org-unescape-code-in-string |
|
1929 |
(buffer-substring-no-properties |
|
1930 |
contents-begin contents-end))) |
|
1931 |
(pos-before-blank (progn (goto-char contents-end) |
|
1932 |
(forward-line) |
|
1933 |
(point))) |
|
1934 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
1935 |
(if (eobp) (point) (line-beginning-position))))) |
|
1936 |
(list 'example-block |
|
1937 |
(nconc |
|
1938 |
(list :begin begin |
|
1939 |
:end end |
|
1940 |
:value value |
|
1941 |
:switches switches |
|
1942 |
:number-lines number-lines |
|
1943 |
:preserve-indent preserve-indent |
|
1944 |
:retain-labels retain-labels |
|
1945 |
:use-labels use-labels |
|
1946 |
:label-fmt label-fmt |
|
1947 |
:post-blank (count-lines pos-before-blank end) |
|
1948 |
:post-affiliated post-affiliated) |
|
1949 |
(cdr affiliated))))))))) |
|
1950 |
|
|
1951 |
(defun org-element-example-block-interpreter (example-block _) |
|
1952 |
"Interpret EXAMPLE-BLOCK element as Org syntax." |
|
1953 |
(let ((switches (org-element-property :switches example-block)) |
|
1954 |
(value (org-element-property :value example-block))) |
|
1955 |
(concat "#+BEGIN_EXAMPLE" (and switches (concat " " switches)) "\n" |
|
1956 |
(org-element-normalize-string |
|
1957 |
(org-escape-code-in-string |
|
1958 |
(if (or org-src-preserve-indentation |
|
1959 |
(org-element-property :preserve-indent example-block)) |
|
1960 |
value |
|
1961 |
(org-remove-indentation value)))) |
|
1962 |
"#+END_EXAMPLE"))) |
|
1963 |
|
|
1964 |
|
|
1965 |
;;;; Export Block |
|
1966 |
|
|
1967 |
(defun org-element-export-block-parser (limit affiliated) |
|
1968 |
"Parse an export block. |
|
1969 |
|
|
1970 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
1971 |
the buffer position at the beginning of the first affiliated |
|
1972 |
keyword and CDR is a plist of affiliated keywords along with |
|
1973 |
their value. |
|
1974 |
|
|
1975 |
Return a list whose CAR is `export-block' and CDR is a plist |
|
1976 |
containing `:begin', `:end', `:type', `:value', `:post-blank' and |
|
1977 |
`:post-affiliated' keywords. |
|
1978 |
|
|
1979 |
Assume point is at export-block beginning." |
|
1980 |
(let* ((case-fold-search t)) |
|
1981 |
(if (not (save-excursion |
|
1982 |
(re-search-forward "^[ \t]*#\\+END_EXPORT[ \t]*$" limit t))) |
|
1983 |
;; Incomplete block: parse it as a paragraph. |
|
1984 |
(org-element-paragraph-parser limit affiliated) |
|
1985 |
(save-excursion |
|
1986 |
(let* ((contents-end (match-beginning 0)) |
|
1987 |
(backend |
|
1988 |
(progn |
|
1989 |
(looking-at |
|
1990 |
"[ \t]*#\\+BEGIN_EXPORT\\(?:[ \t]+\\(\\S-+\\)\\)?[ \t]*$") |
|
1991 |
(match-string-no-properties 1))) |
|
1992 |
(begin (car affiliated)) |
|
1993 |
(post-affiliated (point)) |
|
1994 |
(contents-begin (progn (forward-line) (point))) |
|
1995 |
(pos-before-blank (progn (goto-char contents-end) |
|
1996 |
(forward-line) |
|
1997 |
(point))) |
|
1998 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
1999 |
(if (eobp) (point) (line-beginning-position)))) |
|
2000 |
(value (org-unescape-code-in-string |
|
2001 |
(buffer-substring-no-properties contents-begin |
|
2002 |
contents-end)))) |
|
2003 |
(list 'export-block |
|
2004 |
(nconc |
|
2005 |
(list :type (and backend (upcase backend)) |
|
2006 |
:begin begin |
|
2007 |
:end end |
|
2008 |
:value value |
|
2009 |
:post-blank (count-lines pos-before-blank end) |
|
2010 |
:post-affiliated post-affiliated) |
|
2011 |
(cdr affiliated)))))))) |
|
2012 |
|
|
2013 |
(defun org-element-export-block-interpreter (export-block _) |
|
2014 |
"Interpret EXPORT-BLOCK element as Org syntax." |
|
2015 |
(format "#+BEGIN_EXPORT %s\n%s#+END_EXPORT" |
|
2016 |
(org-element-property :type export-block) |
|
2017 |
(org-element-property :value export-block))) |
|
2018 |
|
|
2019 |
|
|
2020 |
;;;; Fixed-width |
|
2021 |
|
|
2022 |
(defun org-element-fixed-width-parser (limit affiliated) |
|
2023 |
"Parse a fixed-width section. |
|
2024 |
|
|
2025 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2026 |
the buffer position at the beginning of the first affiliated |
|
2027 |
keyword and CDR is a plist of affiliated keywords along with |
|
2028 |
their value. |
|
2029 |
|
|
2030 |
Return a list whose CAR is `fixed-width' and CDR is a plist |
|
2031 |
containing `:begin', `:end', `:value', `:post-blank' and |
|
2032 |
`:post-affiliated' keywords. |
|
2033 |
|
|
2034 |
Assume point is at the beginning of the fixed-width area." |
|
2035 |
(save-excursion |
|
2036 |
(let* ((begin (car affiliated)) |
|
2037 |
(post-affiliated (point)) |
|
2038 |
value |
|
2039 |
(end-area |
|
2040 |
(progn |
|
2041 |
(while (and (< (point) limit) |
|
2042 |
(looking-at "[ \t]*:\\( \\|$\\)")) |
|
2043 |
;; Accumulate text without starting colons. |
|
2044 |
(setq value |
|
2045 |
(concat value |
|
2046 |
(buffer-substring-no-properties |
|
2047 |
(match-end 0) (point-at-eol)) |
|
2048 |
"\n")) |
|
2049 |
(forward-line)) |
|
2050 |
(point))) |
|
2051 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2052 |
(if (eobp) (point) (line-beginning-position))))) |
|
2053 |
(list 'fixed-width |
|
2054 |
(nconc |
|
2055 |
(list :begin begin |
|
2056 |
:end end |
|
2057 |
:value value |
|
2058 |
:post-blank (count-lines end-area end) |
|
2059 |
:post-affiliated post-affiliated) |
|
2060 |
(cdr affiliated)))))) |
|
2061 |
|
|
2062 |
(defun org-element-fixed-width-interpreter (fixed-width _) |
|
2063 |
"Interpret FIXED-WIDTH element as Org syntax." |
|
2064 |
(let ((value (org-element-property :value fixed-width))) |
|
2065 |
(and value |
|
2066 |
(replace-regexp-in-string |
|
2067 |
"^" ": " |
|
2068 |
(if (string-match "\n\\'" value) (substring value 0 -1) value))))) |
|
2069 |
|
|
2070 |
|
|
2071 |
;;;; Horizontal Rule |
|
2072 |
|
|
2073 |
(defun org-element-horizontal-rule-parser (limit affiliated) |
|
2074 |
"Parse an horizontal rule. |
|
2075 |
|
|
2076 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2077 |
the buffer position at the beginning of the first affiliated |
|
2078 |
keyword and CDR is a plist of affiliated keywords along with |
|
2079 |
their value. |
|
2080 |
|
|
2081 |
Return a list whose CAR is `horizontal-rule' and CDR is a plist |
|
2082 |
containing `:begin', `:end', `:post-blank' and `:post-affiliated' |
|
2083 |
keywords." |
|
2084 |
(save-excursion |
|
2085 |
(let ((begin (car affiliated)) |
|
2086 |
(post-affiliated (point)) |
|
2087 |
(post-hr (progn (forward-line) (point))) |
|
2088 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2089 |
(if (eobp) (point) (line-beginning-position))))) |
|
2090 |
(list 'horizontal-rule |
|
2091 |
(nconc |
|
2092 |
(list :begin begin |
|
2093 |
:end end |
|
2094 |
:post-blank (count-lines post-hr end) |
|
2095 |
:post-affiliated post-affiliated) |
|
2096 |
(cdr affiliated)))))) |
|
2097 |
|
|
2098 |
(defun org-element-horizontal-rule-interpreter (&rest _) |
|
2099 |
"Interpret HORIZONTAL-RULE element as Org syntax." |
|
2100 |
"-----") |
|
2101 |
|
|
2102 |
|
|
2103 |
;;;; Keyword |
|
2104 |
|
|
2105 |
(defun org-element-keyword-parser (limit affiliated) |
|
2106 |
"Parse a keyword at point. |
|
2107 |
|
|
2108 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2109 |
the buffer position at the beginning of the first affiliated |
|
2110 |
keyword and CDR is a plist of affiliated keywords along with |
|
2111 |
their value. |
|
2112 |
|
|
2113 |
Return a list whose CAR is `keyword' and CDR is a plist |
|
2114 |
containing `:key', `:value', `:begin', `:end', `:post-blank' and |
|
2115 |
`:post-affiliated' keywords." |
|
2116 |
(save-excursion |
|
2117 |
;; An orphaned affiliated keyword is considered as a regular |
|
2118 |
;; keyword. In this case AFFILIATED is nil, so we take care of |
|
2119 |
;; this corner case. |
|
2120 |
(let ((begin (or (car affiliated) (point))) |
|
2121 |
(post-affiliated (point)) |
|
2122 |
(key (progn (looking-at "[ \t]*#\\+\\(\\S-+*\\):") |
|
2123 |
(upcase (match-string-no-properties 1)))) |
|
2124 |
(value (org-trim (buffer-substring-no-properties |
|
2125 |
(match-end 0) (point-at-eol)))) |
|
2126 |
(pos-before-blank (progn (forward-line) (point))) |
|
2127 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2128 |
(if (eobp) (point) (line-beginning-position))))) |
|
2129 |
(list 'keyword |
|
2130 |
(nconc |
|
2131 |
(list :key key |
|
2132 |
:value value |
|
2133 |
:begin begin |
|
2134 |
:end end |
|
2135 |
:post-blank (count-lines pos-before-blank end) |
|
2136 |
:post-affiliated post-affiliated) |
|
2137 |
(cdr affiliated)))))) |
|
2138 |
|
|
2139 |
(defun org-element-keyword-interpreter (keyword _) |
|
2140 |
"Interpret KEYWORD element as Org syntax." |
|
2141 |
(format "#+%s: %s" |
|
2142 |
(org-element-property :key keyword) |
|
2143 |
(org-element-property :value keyword))) |
|
2144 |
|
|
2145 |
|
|
2146 |
;;;; Latex Environment |
|
2147 |
|
|
2148 |
(defconst org-element--latex-begin-environment |
|
2149 |
"^[ \t]*\\\\begin{\\([A-Za-z0-9*]+\\)}" |
|
2150 |
"Regexp matching the beginning of a LaTeX environment. |
|
2151 |
The environment is captured by the first group. |
|
2152 |
|
|
2153 |
See also `org-element--latex-end-environment'.") |
|
2154 |
|
|
2155 |
(defconst org-element--latex-end-environment |
|
2156 |
"\\\\end{%s}[ \t]*$" |
|
2157 |
"Format string matching the ending of a LaTeX environment. |
|
2158 |
See also `org-element--latex-begin-environment'.") |
|
2159 |
|
|
2160 |
(defun org-element-latex-environment-parser (limit affiliated) |
|
2161 |
"Parse a LaTeX environment. |
|
2162 |
|
|
2163 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2164 |
the buffer position at the beginning of the first affiliated |
|
2165 |
keyword and CDR is a plist of affiliated keywords along with |
|
2166 |
their value. |
|
2167 |
|
|
2168 |
Return a list whose CAR is `latex-environment' and CDR is a plist |
|
2169 |
containing `:begin', `:end', `:value', `:post-blank' and |
|
2170 |
`:post-affiliated' keywords. |
|
2171 |
|
|
2172 |
Assume point is at the beginning of the latex environment." |
|
2173 |
(save-excursion |
|
2174 |
(let ((case-fold-search t) |
|
2175 |
(code-begin (point))) |
|
2176 |
(looking-at org-element--latex-begin-environment) |
|
2177 |
(if (not (re-search-forward (format org-element--latex-end-environment |
|
2178 |
(regexp-quote (match-string 1))) |
|
2179 |
limit t)) |
|
2180 |
;; Incomplete latex environment: parse it as a paragraph. |
|
2181 |
(org-element-paragraph-parser limit affiliated) |
|
2182 |
(let* ((code-end (progn (forward-line) (point))) |
|
2183 |
(begin (car affiliated)) |
|
2184 |
(value (buffer-substring-no-properties code-begin code-end)) |
|
2185 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2186 |
(if (eobp) (point) (line-beginning-position))))) |
|
2187 |
(list 'latex-environment |
|
2188 |
(nconc |
|
2189 |
(list :begin begin |
|
2190 |
:end end |
|
2191 |
:value value |
|
2192 |
:post-blank (count-lines code-end end) |
|
2193 |
:post-affiliated code-begin) |
|
2194 |
(cdr affiliated)))))))) |
|
2195 |
|
|
2196 |
(defun org-element-latex-environment-interpreter (latex-environment _) |
|
2197 |
"Interpret LATEX-ENVIRONMENT element as Org syntax." |
|
2198 |
(org-element-property :value latex-environment)) |
|
2199 |
|
|
2200 |
|
|
2201 |
;;;; Node Property |
|
2202 |
|
|
2203 |
(defun org-element-node-property-parser (limit) |
|
2204 |
"Parse a node-property at point. |
|
2205 |
|
|
2206 |
LIMIT bounds the search. |
|
2207 |
|
|
2208 |
Return a list whose CAR is `node-property' and CDR is a plist |
|
2209 |
containing `:key', `:value', `:begin', `:end', `:post-blank' and |
|
2210 |
`:post-affiliated' keywords." |
|
2211 |
(looking-at org-property-re) |
|
2212 |
(let ((case-fold-search t) |
|
2213 |
(begin (point)) |
|
2214 |
(key (match-string-no-properties 2)) |
|
2215 |
(value (match-string-no-properties 3)) |
|
2216 |
(end (save-excursion |
|
2217 |
(end-of-line) |
|
2218 |
(if (re-search-forward org-property-re limit t) |
|
2219 |
(line-beginning-position) |
|
2220 |
limit)))) |
|
2221 |
(list 'node-property |
|
2222 |
(list :key key |
|
2223 |
:value value |
|
2224 |
:begin begin |
|
2225 |
:end end |
|
2226 |
:post-blank 0 |
|
2227 |
:post-affiliated begin)))) |
|
2228 |
|
|
2229 |
(defun org-element-node-property-interpreter (node-property _) |
|
2230 |
"Interpret NODE-PROPERTY element as Org syntax." |
|
2231 |
(format org-property-format |
|
2232 |
(format ":%s:" (org-element-property :key node-property)) |
|
2233 |
(or (org-element-property :value node-property) ""))) |
|
2234 |
|
|
2235 |
|
|
2236 |
;;;; Paragraph |
|
2237 |
|
|
2238 |
(defun org-element-paragraph-parser (limit affiliated) |
|
2239 |
"Parse a paragraph. |
|
2240 |
|
|
2241 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2242 |
the buffer position at the beginning of the first affiliated |
|
2243 |
keyword and CDR is a plist of affiliated keywords along with |
|
2244 |
their value. |
|
2245 |
|
|
2246 |
Return a list whose CAR is `paragraph' and CDR is a plist |
|
2247 |
containing `:begin', `:end', `:contents-begin' and |
|
2248 |
`:contents-end', `:post-blank' and `:post-affiliated' keywords. |
|
2249 |
|
|
2250 |
Assume point is at the beginning of the paragraph." |
|
2251 |
(save-excursion |
|
2252 |
(let* ((begin (car affiliated)) |
|
2253 |
(contents-begin (point)) |
|
2254 |
(before-blank |
|
2255 |
(let ((case-fold-search t)) |
|
2256 |
(end-of-line) |
|
2257 |
;; A matching `org-element-paragraph-separate' is not |
|
2258 |
;; necessarily the end of the paragraph. In particular, |
|
2259 |
;; drawers, blocks or LaTeX environments opening lines |
|
2260 |
;; must be closed. Moreover keywords with a secondary |
|
2261 |
;; value must belong to "dual keywords". |
|
2262 |
(while (not |
|
2263 |
(cond |
|
2264 |
((not (and (re-search-forward |
|
2265 |
org-element-paragraph-separate limit 'move) |
|
2266 |
(progn (beginning-of-line) t)))) |
|
2267 |
((looking-at org-drawer-regexp) |
|
2268 |
(save-excursion |
|
2269 |
(re-search-forward "^[ \t]*:END:[ \t]*$" limit t))) |
|
2270 |
((looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") |
|
2271 |
(save-excursion |
|
2272 |
(re-search-forward |
|
2273 |
(format "^[ \t]*#\\+END_%s[ \t]*$" |
|
2274 |
(regexp-quote (match-string 1))) |
|
2275 |
limit t))) |
|
2276 |
((looking-at org-element--latex-begin-environment) |
|
2277 |
(save-excursion |
|
2278 |
(re-search-forward |
|
2279 |
(format org-element--latex-end-environment |
|
2280 |
(regexp-quote (match-string 1))) |
|
2281 |
limit t))) |
|
2282 |
((looking-at "[ \t]*#\\+\\(\\S-+\\)\\[.*\\]:") |
|
2283 |
(member-ignore-case (match-string 1) |
|
2284 |
org-element-dual-keywords)) |
|
2285 |
;; Everything else is unambiguous. |
|
2286 |
(t))) |
|
2287 |
(end-of-line)) |
|
2288 |
(if (= (point) limit) limit |
|
2289 |
(goto-char (line-beginning-position))))) |
|
2290 |
(contents-end (save-excursion |
|
2291 |
(skip-chars-backward " \r\t\n" contents-begin) |
|
2292 |
(line-beginning-position 2))) |
|
2293 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2294 |
(if (eobp) (point) (line-beginning-position))))) |
|
2295 |
(list 'paragraph |
|
2296 |
(nconc |
|
2297 |
(list :begin begin |
|
2298 |
:end end |
|
2299 |
:contents-begin contents-begin |
|
2300 |
:contents-end contents-end |
|
2301 |
:post-blank (count-lines before-blank end) |
|
2302 |
:post-affiliated contents-begin) |
|
2303 |
(cdr affiliated)))))) |
|
2304 |
|
|
2305 |
(defun org-element-paragraph-interpreter (_ contents) |
|
2306 |
"Interpret paragraph element as Org syntax. |
|
2307 |
CONTENTS is the contents of the element." |
|
2308 |
contents) |
|
2309 |
|
|
2310 |
|
|
2311 |
;;;; Planning |
|
2312 |
|
|
2313 |
(defun org-element-planning-parser (limit) |
|
2314 |
"Parse a planning. |
|
2315 |
|
|
2316 |
LIMIT bounds the search. |
|
2317 |
|
|
2318 |
Return a list whose CAR is `planning' and CDR is a plist |
|
2319 |
containing `:closed', `:deadline', `:scheduled', `:begin', |
|
2320 |
`:end', `:post-blank' and `:post-affiliated' keywords." |
|
2321 |
(save-excursion |
|
2322 |
(let* ((case-fold-search nil) |
|
2323 |
(begin (point)) |
|
2324 |
(post-blank (let ((before-blank (progn (forward-line) (point)))) |
|
2325 |
(skip-chars-forward " \r\t\n" limit) |
|
2326 |
(skip-chars-backward " \t") |
|
2327 |
(unless (bolp) (end-of-line)) |
|
2328 |
(count-lines before-blank (point)))) |
|
2329 |
(end (point)) |
|
2330 |
closed deadline scheduled) |
|
2331 |
(goto-char begin) |
|
2332 |
(while (re-search-forward org-keyword-time-not-clock-regexp end t) |
|
2333 |
(goto-char (match-end 1)) |
|
2334 |
(skip-chars-forward " \t" end) |
|
2335 |
(let ((keyword (match-string 1)) |
|
2336 |
(time (org-element-timestamp-parser))) |
|
2337 |
(cond ((equal keyword org-closed-string) (setq closed time)) |
|
2338 |
((equal keyword org-deadline-string) (setq deadline time)) |
|
2339 |
(t (setq scheduled time))))) |
|
2340 |
(list 'planning |
|
2341 |
(list :closed closed |
|
2342 |
:deadline deadline |
|
2343 |
:scheduled scheduled |
|
2344 |
:begin begin |
|
2345 |
:end end |
|
2346 |
:post-blank post-blank |
|
2347 |
:post-affiliated begin))))) |
|
2348 |
|
|
2349 |
(defun org-element-planning-interpreter (planning _) |
|
2350 |
"Interpret PLANNING element as Org syntax." |
|
2351 |
(mapconcat |
|
2352 |
#'identity |
|
2353 |
(delq nil |
|
2354 |
(list (let ((deadline (org-element-property :deadline planning))) |
|
2355 |
(when deadline |
|
2356 |
(concat org-deadline-string " " |
|
2357 |
(org-element-timestamp-interpreter deadline nil)))) |
|
2358 |
(let ((scheduled (org-element-property :scheduled planning))) |
|
2359 |
(when scheduled |
|
2360 |
(concat org-scheduled-string " " |
|
2361 |
(org-element-timestamp-interpreter scheduled nil)))) |
|
2362 |
(let ((closed (org-element-property :closed planning))) |
|
2363 |
(when closed |
|
2364 |
(concat org-closed-string " " |
|
2365 |
(org-element-timestamp-interpreter closed nil)))))) |
|
2366 |
" ")) |
|
2367 |
|
|
2368 |
|
|
2369 |
;;;; Src Block |
|
2370 |
|
|
2371 |
(defun org-element-src-block-parser (limit affiliated) |
|
2372 |
"Parse a src block. |
|
2373 |
|
|
2374 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2375 |
the buffer position at the beginning of the first affiliated |
|
2376 |
keyword and CDR is a plist of affiliated keywords along with |
|
2377 |
their value. |
|
2378 |
|
|
2379 |
Return a list whose CAR is `src-block' and CDR is a plist |
|
2380 |
containing `:language', `:switches', `:parameters', `:begin', |
|
2381 |
`:end', `:number-lines', `:retain-labels', `:use-labels', |
|
2382 |
`:label-fmt', `:preserve-indent', `:value', `:post-blank' and |
|
2383 |
`:post-affiliated' keywords. |
|
2384 |
|
|
2385 |
Assume point is at the beginning of the block." |
|
2386 |
(let ((case-fold-search t)) |
|
2387 |
(if (not (save-excursion (re-search-forward "^[ \t]*#\\+END_SRC[ \t]*$" |
|
2388 |
limit t))) |
|
2389 |
;; Incomplete block: parse it as a paragraph. |
|
2390 |
(org-element-paragraph-parser limit affiliated) |
|
2391 |
(let ((contents-end (match-beginning 0))) |
|
2392 |
(save-excursion |
|
2393 |
(let* ((begin (car affiliated)) |
|
2394 |
(post-affiliated (point)) |
|
2395 |
;; Get language as a string. |
|
2396 |
(language |
|
2397 |
(progn |
|
2398 |
(looking-at |
|
2399 |
"^[ \t]*#\\+BEGIN_SRC\ |
|
2400 |
\\(?: +\\(\\S-+\\)\\)?\ |
|
2401 |
\\(\\(?: +\\(?:-\\(?:l \".+\"\\|[ikr]\\)\\|[-+]n\\(?: *[0-9]+\\)?\\)\\)+\\)?\ |
|
2402 |
\\(.*\\)[ \t]*$") |
|
2403 |
(match-string-no-properties 1))) |
|
2404 |
;; Get switches. |
|
2405 |
(switches (match-string-no-properties 2)) |
|
2406 |
;; Get parameters. |
|
2407 |
(parameters (match-string-no-properties 3)) |
|
2408 |
;; Switches analysis. |
|
2409 |
(number-lines |
|
2410 |
(and switches |
|
2411 |
(string-match "\\([-+]\\)n\\(?: *\\([0-9]+\\)\\)?\\>" |
|
2412 |
switches) |
|
2413 |
(cons |
|
2414 |
(if (equal (match-string 1 switches) "-") |
|
2415 |
'new |
|
2416 |
'continued) |
|
2417 |
(if (not (match-end 2)) 0 |
|
2418 |
;; Subtract 1 to give number of lines before |
|
2419 |
;; first line. |
|
2420 |
(1- (string-to-number (match-string 2 switches))))))) |
|
2421 |
(preserve-indent (and switches |
|
2422 |
(string-match "-i\\>" switches))) |
|
2423 |
(label-fmt |
|
2424 |
(and switches |
|
2425 |
(string-match "-l +\"\\([^\"\n]+\\)\"" switches) |
|
2426 |
(match-string 1 switches))) |
|
2427 |
;; Should labels be retained in (or stripped from) |
|
2428 |
;; src blocks? |
|
2429 |
(retain-labels |
|
2430 |
(or (not switches) |
|
2431 |
(not (string-match "-r\\>" switches)) |
|
2432 |
(and number-lines (string-match "-k\\>" switches)))) |
|
2433 |
;; What should code-references use - labels or |
|
2434 |
;; line-numbers? |
|
2435 |
(use-labels |
|
2436 |
(or (not switches) |
|
2437 |
(and retain-labels |
|
2438 |
(not (string-match "-k\\>" switches))))) |
|
2439 |
;; Retrieve code. |
|
2440 |
(value (org-unescape-code-in-string |
|
2441 |
(buffer-substring-no-properties |
|
2442 |
(line-beginning-position 2) contents-end))) |
|
2443 |
(pos-before-blank (progn (goto-char contents-end) |
|
2444 |
(forward-line) |
|
2445 |
(point))) |
|
2446 |
;; Get position after ending blank lines. |
|
2447 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2448 |
(if (eobp) (point) (line-beginning-position))))) |
|
2449 |
(list 'src-block |
|
2450 |
(nconc |
|
2451 |
(list :language language |
|
2452 |
:switches (and (org-string-nw-p switches) |
|
2453 |
(org-trim switches)) |
|
2454 |
:parameters (and (org-string-nw-p parameters) |
|
2455 |
(org-trim parameters)) |
|
2456 |
:begin begin |
|
2457 |
:end end |
|
2458 |
:number-lines number-lines |
|
2459 |
:preserve-indent preserve-indent |
|
2460 |
:retain-labels retain-labels |
|
2461 |
:use-labels use-labels |
|
2462 |
:label-fmt label-fmt |
|
2463 |
:value value |
|
2464 |
:post-blank (count-lines pos-before-blank end) |
|
2465 |
:post-affiliated post-affiliated) |
|
2466 |
(cdr affiliated))))))))) |
|
2467 |
|
|
2468 |
(defun org-element-src-block-interpreter (src-block _) |
|
2469 |
"Interpret SRC-BLOCK element as Org syntax." |
|
2470 |
(let ((lang (org-element-property :language src-block)) |
|
2471 |
(switches (org-element-property :switches src-block)) |
|
2472 |
(params (org-element-property :parameters src-block)) |
|
2473 |
(value |
|
2474 |
(let ((val (org-element-property :value src-block))) |
|
2475 |
(cond |
|
2476 |
((or org-src-preserve-indentation |
|
2477 |
(org-element-property :preserve-indent src-block)) |
|
2478 |
val) |
|
2479 |
((zerop org-edit-src-content-indentation) |
|
2480 |
(org-remove-indentation val)) |
|
2481 |
(t |
|
2482 |
(let ((ind (make-string org-edit-src-content-indentation ?\s))) |
|
2483 |
(replace-regexp-in-string |
|
2484 |
"^" ind (org-remove-indentation val)))))))) |
|
2485 |
(concat (format "#+BEGIN_SRC%s\n" |
|
2486 |
(concat (and lang (concat " " lang)) |
|
2487 |
(and switches (concat " " switches)) |
|
2488 |
(and params (concat " " params)))) |
|
2489 |
(org-element-normalize-string (org-escape-code-in-string value)) |
|
2490 |
"#+END_SRC"))) |
|
2491 |
|
|
2492 |
|
|
2493 |
;;;; Table |
|
2494 |
|
|
2495 |
(defun org-element-table-parser (limit affiliated) |
|
2496 |
"Parse a table at point. |
|
2497 |
|
|
2498 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2499 |
the buffer position at the beginning of the first affiliated |
|
2500 |
keyword and CDR is a plist of affiliated keywords along with |
|
2501 |
their value. |
|
2502 |
|
|
2503 |
Return a list whose CAR is `table' and CDR is a plist containing |
|
2504 |
`:begin', `:end', `:tblfm', `:type', `:contents-begin', |
|
2505 |
`:contents-end', `:value', `:post-blank' and `:post-affiliated' |
|
2506 |
keywords. |
|
2507 |
|
|
2508 |
Assume point is at the beginning of the table." |
|
2509 |
(save-excursion |
|
2510 |
(let* ((case-fold-search t) |
|
2511 |
(table-begin (point)) |
|
2512 |
(type (if (looking-at "[ \t]*|") 'org 'table.el)) |
|
2513 |
(end-re (format "^[ \t]*\\($\\|[^| \t%s]\\)" |
|
2514 |
(if (eq type 'org) "" "+"))) |
|
2515 |
(begin (car affiliated)) |
|
2516 |
(table-end |
|
2517 |
(if (re-search-forward end-re limit 'move) |
|
2518 |
(goto-char (match-beginning 0)) |
|
2519 |
(point))) |
|
2520 |
(tblfm (let (acc) |
|
2521 |
(while (looking-at "[ \t]*#\\+TBLFM: +\\(.*\\)[ \t]*$") |
|
2522 |
(push (match-string-no-properties 1) acc) |
|
2523 |
(forward-line)) |
|
2524 |
acc)) |
|
2525 |
(pos-before-blank (point)) |
|
2526 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2527 |
(if (eobp) (point) (line-beginning-position))))) |
|
2528 |
(list 'table |
|
2529 |
(nconc |
|
2530 |
(list :begin begin |
|
2531 |
:end end |
|
2532 |
:type type |
|
2533 |
:tblfm tblfm |
|
2534 |
;; Only `org' tables have contents. `table.el' tables |
|
2535 |
;; use a `:value' property to store raw table as |
|
2536 |
;; a string. |
|
2537 |
:contents-begin (and (eq type 'org) table-begin) |
|
2538 |
:contents-end (and (eq type 'org) table-end) |
|
2539 |
:value (and (eq type 'table.el) |
|
2540 |
(buffer-substring-no-properties |
|
2541 |
table-begin table-end)) |
|
2542 |
:post-blank (count-lines pos-before-blank end) |
|
2543 |
:post-affiliated table-begin) |
|
2544 |
(cdr affiliated)))))) |
|
2545 |
|
|
2546 |
(defun org-element-table-interpreter (table contents) |
|
2547 |
"Interpret TABLE element as Org syntax. |
|
2548 |
CONTENTS is a string, if table's type is `org', or nil." |
|
2549 |
(if (eq (org-element-property :type table) 'table.el) |
|
2550 |
(org-remove-indentation (org-element-property :value table)) |
|
2551 |
(concat (with-temp-buffer (insert contents) |
|
2552 |
(org-table-align) |
|
2553 |
(buffer-string)) |
|
2554 |
(mapconcat (lambda (fm) (concat "#+TBLFM: " fm)) |
|
2555 |
(reverse (org-element-property :tblfm table)) |
|
2556 |
"\n")))) |
|
2557 |
|
|
2558 |
|
|
2559 |
;;;; Table Row |
|
2560 |
|
|
2561 |
(defun org-element-table-row-parser (_) |
|
2562 |
"Parse table row at point. |
|
2563 |
|
|
2564 |
Return a list whose CAR is `table-row' and CDR is a plist |
|
2565 |
containing `:begin', `:end', `:contents-begin', `:contents-end', |
|
2566 |
`:type', `:post-blank' and `:post-affiliated' keywords." |
|
2567 |
(save-excursion |
|
2568 |
(let* ((type (if (looking-at "^[ \t]*|-") 'rule 'standard)) |
|
2569 |
(begin (point)) |
|
2570 |
;; A table rule has no contents. In that case, ensure |
|
2571 |
;; CONTENTS-BEGIN matches CONTENTS-END. |
|
2572 |
(contents-begin (and (eq type 'standard) (search-forward "|"))) |
|
2573 |
(contents-end (and (eq type 'standard) |
|
2574 |
(progn |
|
2575 |
(end-of-line) |
|
2576 |
(skip-chars-backward " \t") |
|
2577 |
(point)))) |
|
2578 |
(end (line-beginning-position 2))) |
|
2579 |
(list 'table-row |
|
2580 |
(list :type type |
|
2581 |
:begin begin |
|
2582 |
:end end |
|
2583 |
:contents-begin contents-begin |
|
2584 |
:contents-end contents-end |
|
2585 |
:post-blank 0 |
|
2586 |
:post-affiliated begin))))) |
|
2587 |
|
|
2588 |
(defun org-element-table-row-interpreter (table-row contents) |
|
2589 |
"Interpret TABLE-ROW element as Org syntax. |
|
2590 |
CONTENTS is the contents of the table row." |
|
2591 |
(if (eq (org-element-property :type table-row) 'rule) "|-" |
|
2592 |
(concat "|" contents))) |
|
2593 |
|
|
2594 |
|
|
2595 |
;;;; Verse Block |
|
2596 |
|
|
2597 |
(defun org-element-verse-block-parser (limit affiliated) |
|
2598 |
"Parse a verse block. |
|
2599 |
|
|
2600 |
LIMIT bounds the search. AFFILIATED is a list of which CAR is |
|
2601 |
the buffer position at the beginning of the first affiliated |
|
2602 |
keyword and CDR is a plist of affiliated keywords along with |
|
2603 |
their value. |
|
2604 |
|
|
2605 |
Return a list whose CAR is `verse-block' and CDR is a plist |
|
2606 |
containing `:begin', `:end', `:contents-begin', `:contents-end', |
|
2607 |
`:post-blank' and `:post-affiliated' keywords. |
|
2608 |
|
|
2609 |
Assume point is at beginning of the block." |
|
2610 |
(let ((case-fold-search t)) |
|
2611 |
(if (not (save-excursion |
|
2612 |
(re-search-forward "^[ \t]*#\\+END_VERSE[ \t]*$" limit t))) |
|
2613 |
;; Incomplete block: parse it as a paragraph. |
|
2614 |
(org-element-paragraph-parser limit affiliated) |
|
2615 |
(let ((contents-end (match-beginning 0))) |
|
2616 |
(save-excursion |
|
2617 |
(let* ((begin (car affiliated)) |
|
2618 |
(post-affiliated (point)) |
|
2619 |
(contents-begin (progn (forward-line) (point))) |
|
2620 |
(pos-before-blank (progn (goto-char contents-end) |
|
2621 |
(forward-line) |
|
2622 |
(point))) |
|
2623 |
(end (progn (skip-chars-forward " \r\t\n" limit) |
|
2624 |
(if (eobp) (point) (line-beginning-position))))) |
|
2625 |
(list 'verse-block |
|
2626 |
(nconc |
|
2627 |
(list :begin begin |
|
2628 |
:end end |
|
2629 |
:contents-begin contents-begin |
|
2630 |
:contents-end contents-end |
|
2631 |
:post-blank (count-lines pos-before-blank end) |
|
2632 |
:post-affiliated post-affiliated) |
|
2633 |
(cdr affiliated))))))))) |
|
2634 |
|
|
2635 |
(defun org-element-verse-block-interpreter (_ contents) |
|
2636 |
"Interpret verse-block element as Org syntax. |
|
2637 |
CONTENTS is verse block contents." |
|
2638 |
(format "#+BEGIN_VERSE\n%s#+END_VERSE" contents)) |
|
2639 |
|
|
2640 |
|
|
2641 |
|
|
2642 |
;;; Objects |
|
2643 |
;; |
|
2644 |
;; Unlike to elements, raw text can be found between objects. Hence, |
|
2645 |
;; `org-element--object-lex' is provided to find the next object in |
|
2646 |
;; buffer. |
|
2647 |
;; |
|
2648 |
;; Some object types (e.g., `italic') are recursive. Restrictions on |
|
2649 |
;; object types they can contain will be specified in |
|
2650 |
;; `org-element-object-restrictions'. |
|
2651 |
;; |
|
2652 |
;; Creating a new type of object requires to alter |
|
2653 |
;; `org-element--object-regexp' and `org-element--object-lex', add the |
|
2654 |
;; new type in `org-element-all-objects', and possibly add |
|
2655 |
;; restrictions in `org-element-object-restrictions'. |
|
2656 |
|
|
2657 |
;;;; Bold |
|
2658 |
|
|
2659 |
(defun org-element-bold-parser () |
|
2660 |
"Parse bold object at point, if any. |
|
2661 |
|
|
2662 |
When at a bold object, return a list whose car is `bold' and cdr |
|
2663 |
is a plist with `:begin', `:end', `:contents-begin' and |
|
2664 |
`:contents-end' and `:post-blank' keywords. Otherwise, return |
|
2665 |
nil. |
|
2666 |
|
|
2667 |
Assume point is at the first star marker." |
|
2668 |
(save-excursion |
|
2669 |
(unless (bolp) (backward-char 1)) |
|
2670 |
(when (looking-at org-emph-re) |
|
2671 |
(let ((begin (match-beginning 2)) |
|
2672 |
(contents-begin (match-beginning 4)) |
|
2673 |
(contents-end (match-end 4)) |
|
2674 |
(post-blank (progn (goto-char (match-end 2)) |
|
2675 |
(skip-chars-forward " \t"))) |
|
2676 |
(end (point))) |
|
2677 |
(list 'bold |
|
2678 |
(list :begin begin |
|
2679 |
:end end |
|
2680 |
:contents-begin contents-begin |
|
2681 |
:contents-end contents-end |
|
2682 |
:post-blank post-blank)))))) |
|
2683 |
|
|
2684 |
(defun org-element-bold-interpreter (_ contents) |
|
2685 |
"Interpret bold object as Org syntax. |
|
2686 |
CONTENTS is the contents of the object." |
|
2687 |
(format "*%s*" contents)) |
|
2688 |
|
|
2689 |
|
|
2690 |
;;;; Code |
|
2691 |
|
|
2692 |
(defun org-element-code-parser () |
|
2693 |
"Parse code object at point, if any. |
|
2694 |
|
|
2695 |
When at a code object, return a list whose car is `code' and cdr |
|
2696 |
is a plist with `:value', `:begin', `:end' and `:post-blank' |
|
2697 |
keywords. Otherwise, return nil. |
|
2698 |
|
|
2699 |
Assume point is at the first tilde marker." |
|
2700 |
(save-excursion |
|
2701 |
(unless (bolp) (backward-char 1)) |
|
2702 |
(when (looking-at org-verbatim-re) |
|
2703 |
(let ((begin (match-beginning 2)) |
|
2704 |
(value (match-string-no-properties 4)) |
|
2705 |
(post-blank (progn (goto-char (match-end 2)) |
|
2706 |
(skip-chars-forward " \t"))) |
|
2707 |
(end (point))) |
|
2708 |
(list 'code |
|
2709 |
(list :value value |
|
2710 |
:begin begin |
|
2711 |
:end end |
|
2712 |
:post-blank post-blank)))))) |
|
2713 |
|
|
2714 |
(defun org-element-code-interpreter (code _) |
|
2715 |
"Interpret CODE object as Org syntax." |
|
2716 |
(format "~%s~" (org-element-property :value code))) |
|
2717 |
|
|
2718 |
|
|
2719 |
;;;; Entity |
|
2720 |
|
|
2721 |
(defun org-element-entity-parser () |
|
2722 |
"Parse entity at point, if any. |
|
2723 |
|
|
2724 |
When at an entity, return a list whose car is `entity' and cdr |
|
2725 |
a plist with `:begin', `:end', `:latex', `:latex-math-p', |
|
2726 |
`:html', `:latin1', `:utf-8', `:ascii', `:use-brackets-p' and |
|
2727 |
`:post-blank' as keywords. Otherwise, return nil. |
|
2728 |
|
|
2729 |
Assume point is at the beginning of the entity." |
|
2730 |
(catch 'no-object |
|
2731 |
(when (looking-at "\\\\\\(?:\\(?1:_ +\\)\\|\\(?1:there4\\|sup[123]\\|frac[13][24]\\|[a-zA-Z]+\\)\\(?2:$\\|{}\\|[^[:alpha:]]\\)\\)") |
|
2732 |
(save-excursion |
|
2733 |
(let* ((value (or (org-entity-get (match-string 1)) |
|
2734 |
(throw 'no-object nil))) |
|
2735 |
(begin (match-beginning 0)) |
|
2736 |
(bracketsp (string= (match-string 2) "{}")) |
|
2737 |
(post-blank (progn (goto-char (match-end 1)) |
|
2738 |
(when bracketsp (forward-char 2)) |
|
2739 |
(skip-chars-forward " \t"))) |
|
2740 |
(end (point))) |
|
2741 |
(list 'entity |
|
2742 |
(list :name (car value) |
|
2743 |
:latex (nth 1 value) |
|
2744 |
:latex-math-p (nth 2 value) |
|
2745 |
:html (nth 3 value) |
|
2746 |
:ascii (nth 4 value) |
|
2747 |
:latin1 (nth 5 value) |
|
2748 |
:utf-8 (nth 6 value) |
|
2749 |
:begin begin |
|
2750 |
:end end |
|
2751 |
:use-brackets-p bracketsp |
|
2752 |
:post-blank post-blank))))))) |
|
2753 |
|
|
2754 |
(defun org-element-entity-interpreter (entity _) |
|
2755 |
"Interpret ENTITY object as Org syntax." |
|
2756 |
(concat "\\" |
|
2757 |
(org-element-property :name entity) |
|
2758 |
(when (org-element-property :use-brackets-p entity) "{}"))) |
|
2759 |
|
|
2760 |
|
|
2761 |
;;;; Export Snippet |
|
2762 |
|
|
2763 |
(defun org-element-export-snippet-parser () |
|
2764 |
"Parse export snippet at point. |
|
2765 |
|
|
2766 |
When at an export snippet, return a list whose car is |
|
2767 |
`export-snippet' and cdr a plist with `:begin', `:end', |
|
2768 |
`:back-end', `:value' and `:post-blank' as keywords. Otherwise, |
|
2769 |
return nil. |
|
2770 |
|
|
2771 |
Assume point is at the beginning of the snippet." |
|
2772 |
(save-excursion |
|
2773 |
(let (contents-end) |
|
2774 |
(when (and (looking-at "@@\\([-A-Za-z0-9]+\\):") |
|
2775 |
(setq contents-end |
|
2776 |
(save-match-data (goto-char (match-end 0)) |
|
2777 |
(re-search-forward "@@" nil t) |
|
2778 |
(match-beginning 0)))) |
|
2779 |
(let* ((begin (match-beginning 0)) |
|
2780 |
(back-end (match-string-no-properties 1)) |
|
2781 |
(value (buffer-substring-no-properties |
|
2782 |
(match-end 0) contents-end)) |
|
2783 |
(post-blank (skip-chars-forward " \t")) |
|
2784 |
(end (point))) |
|
2785 |
(list 'export-snippet |
|
2786 |
(list :back-end back-end |
|
2787 |
:value value |
|
2788 |
:begin begin |
|
2789 |
:end end |
|
2790 |
:post-blank post-blank))))))) |
|
2791 |
|
|
2792 |
(defun org-element-export-snippet-interpreter (export-snippet _) |
|
2793 |
"Interpret EXPORT-SNIPPET object as Org syntax." |
|
2794 |
(format "@@%s:%s@@" |
|
2795 |
(org-element-property :back-end export-snippet) |
|
2796 |
(org-element-property :value export-snippet))) |
|
2797 |
|
|
2798 |
|
|
2799 |
;;;; Footnote Reference |
|
2800 |
|
|
2801 |
(defun org-element-footnote-reference-parser () |
|
2802 |
"Parse footnote reference at point, if any. |
|
2803 |
|
|
2804 |
When at a footnote reference, return a list whose car is |
|
2805 |
`footnote-reference' and cdr a plist with `:label', `:type', |
|
2806 |
`:begin', `:end', `:content-begin', `:contents-end' and |
|
2807 |
`:post-blank' as keywords. Otherwise, return nil." |
|
2808 |
(when (looking-at org-footnote-re) |
|
2809 |
(let ((closing (with-syntax-table org-element--pair-square-table |
|
2810 |
(ignore-errors (scan-lists (point) 1 0))))) |
|
2811 |
(when closing |
|
2812 |
(save-excursion |
|
2813 |
(let* ((begin (point)) |
|
2814 |
(label (match-string-no-properties 1)) |
|
2815 |
(inner-begin (match-end 0)) |
|
2816 |
(inner-end (1- closing)) |
|
2817 |
(type (if (match-end 2) 'inline 'standard)) |
|
2818 |
(post-blank (progn (goto-char closing) |
|
2819 |
(skip-chars-forward " \t"))) |
|
2820 |
(end (point))) |
|
2821 |
(list 'footnote-reference |
|
2822 |
(list :label label |
|
2823 |
:type type |
|
2824 |
:begin begin |
|
2825 |
:end end |
|
2826 |
:contents-begin (and (eq type 'inline) inner-begin) |
|
2827 |
:contents-end (and (eq type 'inline) inner-end) |
|
2828 |
:post-blank post-blank)))))))) |
|
2829 |
|
|
2830 |
(defun org-element-footnote-reference-interpreter (footnote-reference contents) |
|
2831 |
"Interpret FOOTNOTE-REFERENCE object as Org syntax. |
|
2832 |
CONTENTS is its definition, when inline, or nil." |
|
2833 |
(format "[fn:%s%s]" |
|
2834 |
(or (org-element-property :label footnote-reference) "") |
|
2835 |
(if contents (concat ":" contents) ""))) |
|
2836 |
|
|
2837 |
|
|
2838 |
;;;; Inline Babel Call |
|
2839 |
|
|
2840 |
(defun org-element-inline-babel-call-parser () |
|
2841 |
"Parse inline babel call at point, if any. |
|
2842 |
|
|
2843 |
When at an inline babel call, return a list whose car is |
|
2844 |
`inline-babel-call' and cdr a plist with `:call', |
|
2845 |
`:inside-header', `:arguments', `:end-header', `:begin', `:end', |
|
2846 |
`:value' and `:post-blank' as keywords. Otherwise, return nil. |
|
2847 |
|
|
2848 |
Assume point is at the beginning of the babel call." |
|
2849 |
(save-excursion |
|
2850 |
(catch :no-object |
|
2851 |
(when (let ((case-fold-search nil)) |
|
2852 |
(looking-at "\\<call_\\([^ \t\n[(]+\\)[([]")) |
|
2853 |
(goto-char (match-end 1)) |
|
2854 |
(let* ((begin (match-beginning 0)) |
|
2855 |
(call (match-string-no-properties 1)) |
|
2856 |
(inside-header |
|
2857 |
(let ((p (org-element--parse-paired-brackets ?\[))) |
|
2858 |
(and (org-string-nw-p p) |
|
2859 |
(replace-regexp-in-string "\n[ \t]*" " " (org-trim p))))) |
|
2860 |
(arguments (org-string-nw-p |
|
2861 |
(or (org-element--parse-paired-brackets ?\() |
|
2862 |
;; Parenthesis are mandatory. |
|
2863 |
(throw :no-object nil)))) |
|
2864 |
(end-header |
|
2865 |
(let ((p (org-element--parse-paired-brackets ?\[))) |
|
2866 |
(and (org-string-nw-p p) |
|
2867 |
(replace-regexp-in-string "\n[ \t]*" " " (org-trim p))))) |
|
2868 |
(value (buffer-substring-no-properties begin (point))) |
|
2869 |
(post-blank (skip-chars-forward " \t")) |
|
2870 |
(end (point))) |
|
2871 |
(list 'inline-babel-call |
|
2872 |
(list :call call |
|
2873 |
:inside-header inside-header |
|
2874 |
:arguments arguments |
|
2875 |
:end-header end-header |
|
2876 |
:begin begin |
|
2877 |
:end end |
|
2878 |
:value value |
|
2879 |
:post-blank post-blank))))))) |
|
2880 |
|
|
2881 |
(defun org-element-inline-babel-call-interpreter (inline-babel-call _) |
|
2882 |
"Interpret INLINE-BABEL-CALL object as Org syntax." |
|
2883 |
(concat "call_" |
|
2884 |
(org-element-property :call inline-babel-call) |
|
2885 |
(let ((h (org-element-property :inside-header inline-babel-call))) |
|
2886 |
(and h (format "[%s]" h))) |
|
2887 |
"(" (org-element-property :arguments inline-babel-call) ")" |
|
2888 |
(let ((h (org-element-property :end-header inline-babel-call))) |
|
2889 |
(and h (format "[%s]" h))))) |
|
2890 |
|
|
2891 |
|
|
2892 |
;;;; Inline Src Block |
|
2893 |
|
|
2894 |
(defun org-element-inline-src-block-parser () |
|
2895 |
"Parse inline source block at point, if any. |
|
2896 |
|
|
2897 |
When at an inline source block, return a list whose car is |
|
2898 |
`inline-src-block' and cdr a plist with `:begin', `:end', |
|
2899 |
`:language', `:value', `:parameters' and `:post-blank' as |
|
2900 |
keywords. Otherwise, return nil. |
|
2901 |
|
|
2902 |
Assume point is at the beginning of the inline src block." |
|
2903 |
(save-excursion |
|
2904 |
(catch :no-object |
|
2905 |
(when (let ((case-fold-search nil)) |
|
2906 |
(looking-at "\\<src_\\([^ \t\n[{]+\\)[{[]")) |
|
2907 |
(goto-char (match-end 1)) |
|
2908 |
(let ((begin (match-beginning 0)) |
|
2909 |
(language (match-string-no-properties 1)) |
|
2910 |
(parameters |
|
2911 |
(let ((p (org-element--parse-paired-brackets ?\[))) |
|
2912 |
(and (org-string-nw-p p) |
|
2913 |
(replace-regexp-in-string "\n[ \t]*" " " (org-trim p))))) |
|
2914 |
(value (or (org-element--parse-paired-brackets ?\{) |
|
2915 |
(throw :no-object nil))) |
|
2916 |
(post-blank (skip-chars-forward " \t"))) |
|
2917 |
(list 'inline-src-block |
|
2918 |
(list :language language |
|
2919 |
:value value |
|
2920 |
:parameters parameters |
|
2921 |
:begin begin |
|
2922 |
:end (point) |
|
2923 |
:post-blank post-blank))))))) |
|
2924 |
|
|
2925 |
(defun org-element-inline-src-block-interpreter (inline-src-block _) |
|
2926 |
"Interpret INLINE-SRC-BLOCK object as Org syntax." |
|
2927 |
(let ((language (org-element-property :language inline-src-block)) |
|
2928 |
(arguments (org-element-property :parameters inline-src-block)) |
|
2929 |
(body (org-element-property :value inline-src-block))) |
|
2930 |
(format "src_%s%s{%s}" |
|
2931 |
language |
|
2932 |
(if arguments (format "[%s]" arguments) "") |
|
2933 |
body))) |
|
2934 |
|
|
2935 |
;;;; Italic |
|
2936 |
|
|
2937 |
(defun org-element-italic-parser () |
|
2938 |
"Parse italic object at point, if any. |
|
2939 |
|
|
2940 |
When at an italic object, return a list whose car is `italic' and |
|
2941 |
cdr is a plist with `:begin', `:end', `:contents-begin' and |
|
2942 |
`:contents-end' and `:post-blank' keywords. Otherwise, return |
|
2943 |
nil. |
|
2944 |
|
|
2945 |
Assume point is at the first slash marker." |
|
2946 |
(save-excursion |
|
2947 |
(unless (bolp) (backward-char 1)) |
|
2948 |
(when (looking-at org-emph-re) |
|
2949 |
(let ((begin (match-beginning 2)) |
|
2950 |
(contents-begin (match-beginning 4)) |
|
2951 |
(contents-end (match-end 4)) |
|
2952 |
(post-blank (progn (goto-char (match-end 2)) |
|
2953 |
(skip-chars-forward " \t"))) |
|
2954 |
(end (point))) |
|
2955 |
(list 'italic |
|
2956 |
(list :begin begin |
|
2957 |
:end end |
|
2958 |
:contents-begin contents-begin |
|
2959 |
:contents-end contents-end |
|
2960 |
:post-blank post-blank)))))) |
|
2961 |
|
|
2962 |
(defun org-element-italic-interpreter (_ contents) |
|
2963 |
"Interpret italic object as Org syntax. |
|
2964 |
CONTENTS is the contents of the object." |
|
2965 |
(format "/%s/" contents)) |
|
2966 |
|
|
2967 |
|
|
2968 |
;;;; Latex Fragment |
|
2969 |
|
|
2970 |
(defun org-element-latex-fragment-parser () |
|
2971 |
"Parse LaTeX fragment at point, if any. |
|
2972 |
|
|
2973 |
When at a LaTeX fragment, return a list whose car is |
|
2974 |
`latex-fragment' and cdr a plist with `:value', `:begin', `:end', |
|
2975 |
and `:post-blank' as keywords. Otherwise, return nil. |
|
2976 |
|
|
2977 |
Assume point is at the beginning of the LaTeX fragment." |
|
2978 |
(catch 'no-object |
|
2979 |
(save-excursion |
|
2980 |
(let* ((begin (point)) |
|
2981 |
(after-fragment |
|
2982 |
(cond |
|
2983 |
((not (eq ?$ (char-after))) |
|
2984 |
(pcase (char-after (1+ (point))) |
|
2985 |
(?\( (search-forward "\\)" nil t)) |
|
2986 |
(?\[ (search-forward "\\]" nil t)) |
|
2987 |
(_ |
|
2988 |
;; Macro. |
|
2989 |
(and (looking-at "\\\\[a-zA-Z]+\\*?\\(\\(\\[[^][\n{}]*\\]\\)\ |
|
2990 |
\\|\\({[^{}\n]*}\\)\\)*") |
|
2991 |
(match-end 0))))) |
|
2992 |
((eq ?$ (char-after (1+ (point)))) |
|
2993 |
(search-forward "$$" nil t 2)) |
|
2994 |
(t |
|
2995 |
(and (not (eq ?$ (char-before))) |
|
2996 |
(not (memq (char-after (1+ (point))) |
|
2997 |
'(?\s ?\t ?\n ?, ?. ?\;))) |
|
2998 |
(search-forward "$" nil t 2) |
|
2999 |
(not (memq (char-before (match-beginning 0)) |
|
3000 |
'(?\s ?\t ?\n ?, ?.))) |
|
3001 |
(looking-at-p |
|
3002 |
"\\(\\s.\\|\\s-\\|\\s(\\|\\s)\\|\\s\"\\|'\\|$\\)") |
|
3003 |
(point))))) |
|
3004 |
(post-blank |
|
3005 |
(if (not after-fragment) (throw 'no-object nil) |
|
3006 |
(goto-char after-fragment) |
|
3007 |
(skip-chars-forward " \t"))) |
|
3008 |
(end (point))) |
|
3009 |
(list 'latex-fragment |
|
3010 |
(list :value (buffer-substring-no-properties begin after-fragment) |
|
3011 |
:begin begin |
|
3012 |
:end end |
|
3013 |
:post-blank post-blank)))))) |
|
3014 |
|
|
3015 |
(defun org-element-latex-fragment-interpreter (latex-fragment _) |
|
3016 |
"Interpret LATEX-FRAGMENT object as Org syntax." |
|
3017 |
(org-element-property :value latex-fragment)) |
|
3018 |
|
|
3019 |
;;;; Line Break |
|
3020 |
|
|
3021 |
(defun org-element-line-break-parser () |
|
3022 |
"Parse line break at point, if any. |
|
3023 |
|
|
3024 |
When at a line break, return a list whose car is `line-break', |
|
3025 |
and cdr a plist with `:begin', `:end' and `:post-blank' keywords. |
|
3026 |
Otherwise, return nil. |
|
3027 |
|
|
3028 |
Assume point is at the beginning of the line break." |
|
3029 |
(when (and (looking-at-p "\\\\\\\\[ \t]*$") |
|
3030 |
(not (eq (char-before) ?\\))) |
|
3031 |
(list 'line-break |
|
3032 |
(list :begin (point) |
|
3033 |
:end (line-beginning-position 2) |
|
3034 |
:post-blank 0)))) |
|
3035 |
|
|
3036 |
(defun org-element-line-break-interpreter (&rest _) |
|
3037 |
"Interpret LINE-BREAK object as Org syntax." |
|
3038 |
"\\\\\n") |
|
3039 |
|
|
3040 |
|
|
3041 |
;;;; Link |
|
3042 |
|
|
3043 |
(defun org-element-link-parser () |
|
3044 |
"Parse link at point, if any. |
|
3045 |
|
|
3046 |
When at a link, return a list whose car is `link' and cdr a plist |
|
3047 |
with `:type', `:path', `:format', `:raw-link', `:application', |
|
3048 |
`:search-option', `:begin', `:end', `:contents-begin', |
|
3049 |
`:contents-end' and `:post-blank' as keywords. Otherwise, return |
|
3050 |
nil. |
|
3051 |
|
|
3052 |
Assume point is at the beginning of the link." |
|
3053 |
(catch 'no-object |
|
3054 |
(let ((begin (point)) |
|
3055 |
end contents-begin contents-end link-end post-blank path type format |
|
3056 |
raw-link search-option application) |
|
3057 |
(cond |
|
3058 |
;; Type 1: Text targeted from a radio target. |
|
3059 |
((and org-target-link-regexp |
|
3060 |
(save-excursion (or (bolp) (backward-char)) |
|
3061 |
(looking-at org-target-link-regexp))) |
|
3062 |
(setq type "radio") |
|
3063 |
(setq format 'plain) |
|
3064 |
(setq link-end (match-end 1)) |
|
3065 |
(setq path (match-string-no-properties 1)) |
|
3066 |
(setq contents-begin (match-beginning 1)) |
|
3067 |
(setq contents-end (match-end 1))) |
|
3068 |
;; Type 2: Standard link, i.e. [[https://orgmode.org][homepage]] |
|
3069 |
((looking-at org-bracket-link-regexp) |
|
3070 |
(setq format 'bracket) |
|
3071 |
(setq contents-begin (match-beginning 3)) |
|
3072 |
(setq contents-end (match-end 3)) |
|
3073 |
(setq link-end (match-end 0)) |
|
3074 |
;; RAW-LINK is the original link. Expand any |
|
3075 |
;; abbreviation in it. |
|
3076 |
;; |
|
3077 |
;; Also treat any newline character and associated |
|
3078 |
;; indentation as a single space character. This is not |
|
3079 |
;; compatible with RFC 3986, which requires to ignore |
|
3080 |
;; them altogether. However, doing so would require |
|
3081 |
;; users to encode spaces on the fly when writing links |
|
3082 |
;; (e.g., insert [[shell:ls%20*.org]] instead of |
|
3083 |
;; [[shell:ls *.org]], which defeats Org's focus on |
|
3084 |
;; simplicity. |
|
3085 |
(setq raw-link (org-link-expand-abbrev |
|
3086 |
(replace-regexp-in-string |
|
3087 |
"[ \t]*\n[ \t]*" " " |
|
3088 |
(match-string-no-properties 1)))) |
|
3089 |
;; Determine TYPE of link and set PATH accordingly. According |
|
3090 |
;; to RFC 3986, remove whitespaces from URI in external links. |
|
3091 |
;; In internal ones, treat indentation as a single space. |
|
3092 |
(cond |
|
3093 |
;; File type. |
|
3094 |
((or (file-name-absolute-p raw-link) |
|
3095 |
(string-match "\\`\\.\\.?/" raw-link)) |
|
3096 |
(setq type "file") |
|
3097 |
(setq path raw-link)) |
|
3098 |
;; Explicit type (http, irc, bbdb...). |
|
3099 |
((string-match org-link-types-re raw-link) |
|
3100 |
(setq type (match-string 1 raw-link)) |
|
3101 |
(setq path (substring raw-link (match-end 0)))) |
|
3102 |
;; Code-ref type: PATH is the name of the reference. |
|
3103 |
((and (string-match-p "\\`(" raw-link) |
|
3104 |
(string-match-p ")\\'" raw-link)) |
|
3105 |
(setq type "coderef") |
|
3106 |
(setq path (substring raw-link 1 -1))) |
|
3107 |
;; Custom-id type: PATH is the name of the custom id. |
|
3108 |
((= (string-to-char raw-link) ?#) |
|
3109 |
(setq type "custom-id") |
|
3110 |
(setq path (substring raw-link 1))) |
|
3111 |
;; Fuzzy type: Internal link either matches a target, an |
|
3112 |
;; headline name or nothing. PATH is the target or |
|
3113 |
;; headline's name. |
|
3114 |
(t |
|
3115 |
(setq type "fuzzy") |
|
3116 |
(setq path raw-link)))) |
|
3117 |
;; Type 3: Plain link, e.g., https://orgmode.org |
|
3118 |
((looking-at org-plain-link-re) |
|
3119 |
(setq format 'plain) |
|
3120 |
(setq raw-link (match-string-no-properties 0)) |
|
3121 |
(setq type (match-string-no-properties 1)) |
|
3122 |
(setq link-end (match-end 0)) |
|
3123 |
(setq path (match-string-no-properties 2))) |
|
3124 |
;; Type 4: Angular link, e.g., <https://orgmode.org>. Unlike to |
|
3125 |
;; bracket links, follow RFC 3986 and remove any extra |
|
3126 |
;; whitespace in URI. |
|
3127 |
((looking-at org-angle-link-re) |
|
3128 |
(setq format 'angle) |
|
3129 |
(setq type (match-string-no-properties 1)) |
|
3130 |
(setq link-end (match-end 0)) |
|
3131 |
(setq raw-link |
|
3132 |
(buffer-substring-no-properties |
|
3133 |
(match-beginning 1) (match-end 2))) |
|
3134 |
(setq path (replace-regexp-in-string |
|
3135 |
"[ \t]*\n[ \t]*" "" (match-string-no-properties 2)))) |
|
3136 |
(t (throw 'no-object nil))) |
|
3137 |
;; In any case, deduce end point after trailing white space from |
|
3138 |
;; LINK-END variable. |
|
3139 |
(save-excursion |
|
3140 |
(setq post-blank |
|
3141 |
(progn (goto-char link-end) (skip-chars-forward " \t"))) |
|
3142 |
(setq end (point))) |
|
3143 |
;; Special "file" type link processing. Extract opening |
|
3144 |
;; application and search option, if any. Also normalize URI. |
|
3145 |
(when (string-match "\\`file\\(?:\\+\\(.+\\)\\)?\\'" type) |
|
3146 |
(setq application (match-string 1 type) type "file") |
|
3147 |
(when (string-match "::\\(.*\\)\\'" path) |
|
3148 |
(setq search-option (match-string 1 path)) |
|
3149 |
(setq path (replace-match "" nil nil path))) |
|
3150 |
(setq path (replace-regexp-in-string "\\`///*\\(.:\\)?/" "\\1/" path))) |
|
3151 |
;; Translate link, if `org-link-translation-function' is set. |
|
3152 |
(let ((trans (and (functionp org-link-translation-function) |
|
3153 |
(funcall org-link-translation-function type path)))) |
|
3154 |
(when trans |
|
3155 |
(setq type (car trans)) |
|
3156 |
(setq path (cdr trans)))) |
|
3157 |
(list 'link |
|
3158 |
(list :type type |
|
3159 |
:path path |
|
3160 |
:format format |
|
3161 |
:raw-link (or raw-link path) |
|
3162 |
:application application |
|
3163 |
:search-option search-option |
|
3164 |
:begin begin |
|
3165 |
:end end |
|
3166 |
:contents-begin contents-begin |
|
3167 |
:contents-end contents-end |
|
3168 |
:post-blank post-blank))))) |
|
3169 |
|
|
3170 |
(defun org-element-link-interpreter (link contents) |
|
3171 |
"Interpret LINK object as Org syntax. |
|
3172 |
CONTENTS is the contents of the object, or nil." |
|
3173 |
(let ((type (org-element-property :type link)) |
|
3174 |
(path (org-element-property :path link))) |
|
3175 |
(if (string= type "radio") path |
|
3176 |
(let ((fmt (pcase (org-element-property :format link) |
|
3177 |
;; Links with contents and internal links have to |
|
3178 |
;; use bracket syntax. Ignore `:format' in these |
|
3179 |
;; cases. This is also the default syntax when the |
|
3180 |
;; property is not defined, e.g., when the object |
|
3181 |
;; was crafted by the user. |
|
3182 |
((guard contents) |
|
3183 |
(format "[[%%s][%s]]" |
|
3184 |
;; Since this is going to be used as |
|
3185 |
;; a format string, escape percent signs |
|
3186 |
;; in description. |
|
3187 |
(replace-regexp-in-string "%" "%%" contents))) |
|
3188 |
((or `bracket |
|
3189 |
`nil |
|
3190 |
(guard (member type '("coderef" "custom-id" "fuzzy")))) |
|
3191 |
"[[%s]]") |
|
3192 |
;; Otherwise, just obey to `:format'. |
|
3193 |
(`angle "<%s>") |
|
3194 |
(`plain "%s") |
|
3195 |
(f (error "Wrong `:format' value: %s" f))))) |
|
3196 |
(format fmt |
|
3197 |
(pcase type |
|
3198 |
("coderef" (format "(%s)" path)) |
|
3199 |
("custom-id" (concat "#" path)) |
|
3200 |
("file" |
|
3201 |
(let ((app (org-element-property :application link)) |
|
3202 |
(opt (org-element-property :search-option link))) |
|
3203 |
(concat type (and app (concat "+" app)) ":" |
|
3204 |
path |
|
3205 |
(and opt (concat "::" opt))))) |
|
3206 |
("fuzzy" path) |
|
3207 |
(_ (concat type ":" path)))))))) |
|
3208 |
|
|
3209 |
|
|
3210 |
;;;; Macro |
|
3211 |
|
|
3212 |
(defun org-element-macro-parser () |
|
3213 |
"Parse macro at point, if any. |
|
3214 |
|
|
3215 |
When at a macro, return a list whose car is `macro' and cdr |
|
3216 |
a plist with `:key', `:args', `:begin', `:end', `:value' and |
|
3217 |
`:post-blank' as keywords. Otherwise, return nil. |
|
3218 |
|
|
3219 |
Assume point is at the macro." |
|
3220 |
(save-excursion |
|
3221 |
(when (looking-at "{{{\\([a-zA-Z][-a-zA-Z0-9_]*\\)\\(([ \t\n]*\\([^\000]*?\\))\\)?}}}") |
|
3222 |
(let ((begin (point)) |
|
3223 |
(key (downcase (match-string-no-properties 1))) |
|
3224 |
(value (match-string-no-properties 0)) |
|
3225 |
(post-blank (progn (goto-char (match-end 0)) |
|
3226 |
(skip-chars-forward " \t"))) |
|
3227 |
(end (point)) |
|
3228 |
(args (let ((args (match-string-no-properties 3))) |
|
3229 |
(and args (org-macro-extract-arguments args))))) |
|
3230 |
(list 'macro |
|
3231 |
(list :key key |
|
3232 |
:value value |
|
3233 |
:args args |
|
3234 |
:begin begin |
|
3235 |
:end end |
|
3236 |
:post-blank post-blank)))))) |
|
3237 |
|
|
3238 |
(defun org-element-macro-interpreter (macro _) |
|
3239 |
"Interpret MACRO object as Org syntax." |
|
3240 |
(org-element-property :value macro)) |
|
3241 |
|
|
3242 |
|
|
3243 |
;;;; Radio-target |
|
3244 |
|
|
3245 |
(defun org-element-radio-target-parser () |
|
3246 |
"Parse radio target at point, if any. |
|
3247 |
|
|
3248 |
When at a radio target, return a list whose car is `radio-target' |
|
3249 |
and cdr a plist with `:begin', `:end', `:contents-begin', |
|
3250 |
`:contents-end', `:value' and `:post-blank' as keywords. |
|
3251 |
Otherwise, return nil. |
|
3252 |
|
|
3253 |
Assume point is at the radio target." |
|
3254 |
(save-excursion |
|
3255 |
(when (looking-at org-radio-target-regexp) |
|
3256 |
(let ((begin (point)) |
|
3257 |
(contents-begin (match-beginning 1)) |
|
3258 |
(contents-end (match-end 1)) |
|
3259 |
(value (match-string-no-properties 1)) |
|
3260 |
(post-blank (progn (goto-char (match-end 0)) |
|
3261 |
(skip-chars-forward " \t"))) |
|
3262 |
(end (point))) |
|
3263 |
(list 'radio-target |
|
3264 |
(list :begin begin |
|
3265 |
:end end |
|
3266 |
:contents-begin contents-begin |
|
3267 |
:contents-end contents-end |
|
3268 |
:post-blank post-blank |
|
3269 |
:value value)))))) |
|
3270 |
|
|
3271 |
(defun org-element-radio-target-interpreter (_ contents) |
|
3272 |
"Interpret target object as Org syntax. |
|
3273 |
CONTENTS is the contents of the object." |
|
3274 |
(concat "<<<" contents ">>>")) |
|
3275 |
|
|
3276 |
|
|
3277 |
;;;; Statistics Cookie |
|
3278 |
|
|
3279 |
(defun org-element-statistics-cookie-parser () |
|
3280 |
"Parse statistics cookie at point, if any. |
|
3281 |
|
|
3282 |
When at a statistics cookie, return a list whose car is |
|
3283 |
`statistics-cookie', and cdr a plist with `:begin', `:end', |
|
3284 |
`:value' and `:post-blank' keywords. Otherwise, return nil. |
|
3285 |
|
|
3286 |
Assume point is at the beginning of the statistics-cookie." |
|
3287 |
(save-excursion |
|
3288 |
(when (looking-at "\\[[0-9]*\\(%\\|/[0-9]*\\)\\]") |
|
3289 |
(let* ((begin (point)) |
|
3290 |
(value (buffer-substring-no-properties |
|
3291 |
(match-beginning 0) (match-end 0))) |
|
3292 |
(post-blank (progn (goto-char (match-end 0)) |
|
3293 |
(skip-chars-forward " \t"))) |
|
3294 |
(end (point))) |
|
3295 |
(list 'statistics-cookie |
|
3296 |
(list :begin begin |
|
3297 |
:end end |
|
3298 |
:value value |
|
3299 |
:post-blank post-blank)))))) |
|
3300 |
|
|
3301 |
(defun org-element-statistics-cookie-interpreter (statistics-cookie _) |
|
3302 |
"Interpret STATISTICS-COOKIE object as Org syntax." |
|
3303 |
(org-element-property :value statistics-cookie)) |
|
3304 |
|
|
3305 |
|
|
3306 |
;;;; Strike-Through |
|
3307 |
|
|
3308 |
(defun org-element-strike-through-parser () |
|
3309 |
"Parse strike-through object at point, if any. |
|
3310 |
|
|
3311 |
When at a strike-through object, return a list whose car is |
|
3312 |
`strike-through' and cdr is a plist with `:begin', `:end', |
|
3313 |
`:contents-begin' and `:contents-end' and `:post-blank' keywords. |
|
3314 |
Otherwise, return nil. |
|
3315 |
|
|
3316 |
Assume point is at the first plus sign marker." |
|
3317 |
(save-excursion |
|
3318 |
(unless (bolp) (backward-char 1)) |
|
3319 |
(when (looking-at org-emph-re) |
|
3320 |
(let ((begin (match-beginning 2)) |
|
3321 |
(contents-begin (match-beginning 4)) |
|
3322 |
(contents-end (match-end 4)) |
|
3323 |
(post-blank (progn (goto-char (match-end 2)) |
|
3324 |
(skip-chars-forward " \t"))) |
|
3325 |
(end (point))) |
|
3326 |
(list 'strike-through |
|
3327 |
(list :begin begin |
|
3328 |
:end end |
|
3329 |
:contents-begin contents-begin |
|
3330 |
:contents-end contents-end |
|
3331 |
:post-blank post-blank)))))) |
|
3332 |
|
|
3333 |
(defun org-element-strike-through-interpreter (_ contents) |
|
3334 |
"Interpret strike-through object as Org syntax. |
|
3335 |
CONTENTS is the contents of the object." |
|
3336 |
(format "+%s+" contents)) |
|
3337 |
|
|
3338 |
|
|
3339 |
;;;; Subscript |
|
3340 |
|
|
3341 |
(defun org-element-subscript-parser () |
|
3342 |
"Parse subscript at point, if any. |
|
3343 |
|
|
3344 |
When at a subscript object, return a list whose car is |
|
3345 |
`subscript' and cdr a plist with `:begin', `:end', |
|
3346 |
`:contents-begin', `:contents-end', `:use-brackets-p' and |
|
3347 |
`:post-blank' as keywords. Otherwise, return nil. |
|
3348 |
|
|
3349 |
Assume point is at the underscore." |
|
3350 |
(save-excursion |
|
3351 |
(unless (bolp) (backward-char)) |
|
3352 |
(when (looking-at org-match-substring-regexp) |
|
3353 |
(let ((bracketsp (match-beginning 4)) |
|
3354 |
(begin (match-beginning 2)) |
|
3355 |
(contents-begin (or (match-beginning 4) |
|
3356 |
(match-beginning 3))) |
|
3357 |
(contents-end (or (match-end 4) (match-end 3))) |
|
3358 |
(post-blank (progn (goto-char (match-end 0)) |
|
3359 |
(skip-chars-forward " \t"))) |
|
3360 |
(end (point))) |
|
3361 |
(list 'subscript |
|
3362 |
(list :begin begin |
|
3363 |
:end end |
|
3364 |
:use-brackets-p bracketsp |
|
3365 |
:contents-begin contents-begin |
|
3366 |
:contents-end contents-end |
|
3367 |
:post-blank post-blank)))))) |
|
3368 |
|
|
3369 |
(defun org-element-subscript-interpreter (subscript contents) |
|
3370 |
"Interpret SUBSCRIPT object as Org syntax. |
|
3371 |
CONTENTS is the contents of the object." |
|
3372 |
(format |
|
3373 |
(if (org-element-property :use-brackets-p subscript) "_{%s}" "_%s") |
|
3374 |
contents)) |
|
3375 |
|
|
3376 |
|
|
3377 |
;;;; Superscript |
|
3378 |
|
|
3379 |
(defun org-element-superscript-parser () |
|
3380 |
"Parse superscript at point, if any. |
|
3381 |
|
|
3382 |
When at a superscript object, return a list whose car is |
|
3383 |
`superscript' and cdr a plist with `:begin', `:end', |
|
3384 |
`:contents-begin', `:contents-end', `:use-brackets-p' and |
|
3385 |
`:post-blank' as keywords. Otherwise, return nil. |
|
3386 |
|
|
3387 |
Assume point is at the caret." |
|
3388 |
(save-excursion |
|
3389 |
(unless (bolp) (backward-char)) |
|
3390 |
(when (looking-at org-match-substring-regexp) |
|
3391 |
(let ((bracketsp (match-beginning 4)) |
|
3392 |
(begin (match-beginning 2)) |
|
3393 |
(contents-begin (or (match-beginning 4) |
|
3394 |
(match-beginning 3))) |
|
3395 |
(contents-end (or (match-end 4) (match-end 3))) |
|
3396 |
(post-blank (progn (goto-char (match-end 0)) |
|
3397 |
(skip-chars-forward " \t"))) |
|
3398 |
(end (point))) |
|
3399 |
(list 'superscript |
|
3400 |
(list :begin begin |
|
3401 |
:end end |
|
3402 |
:use-brackets-p bracketsp |
|
3403 |
:contents-begin contents-begin |
|
3404 |
:contents-end contents-end |
|
3405 |
:post-blank post-blank)))))) |
|
3406 |
|
|
3407 |
(defun org-element-superscript-interpreter (superscript contents) |
|
3408 |
"Interpret SUPERSCRIPT object as Org syntax. |
|
3409 |
CONTENTS is the contents of the object." |
|
3410 |
(format |
|
3411 |
(if (org-element-property :use-brackets-p superscript) "^{%s}" "^%s") |
|
3412 |
contents)) |
|
3413 |
|
|
3414 |
|
|
3415 |
;;;; Table Cell |
|
3416 |
|
|
3417 |
(defun org-element-table-cell-parser () |
|
3418 |
"Parse table cell at point. |
|
3419 |
Return a list whose car is `table-cell' and cdr is a plist |
|
3420 |
containing `:begin', `:end', `:contents-begin', `:contents-end' |
|
3421 |
and `:post-blank' keywords." |
|
3422 |
(looking-at "[ \t]*\\(.*?\\)[ \t]*\\(?:|\\|$\\)") |
|
3423 |
(let* ((begin (match-beginning 0)) |
|
3424 |
(end (match-end 0)) |
|
3425 |
(contents-begin (match-beginning 1)) |
|
3426 |
(contents-end (match-end 1))) |
|
3427 |
(list 'table-cell |
|
3428 |
(list :begin begin |
|
3429 |
:end end |
|
3430 |
:contents-begin contents-begin |
|
3431 |
:contents-end contents-end |
|
3432 |
:post-blank 0)))) |
|
3433 |
|
|
3434 |
(defun org-element-table-cell-interpreter (_ contents) |
|
3435 |
"Interpret table-cell element as Org syntax. |
|
3436 |
CONTENTS is the contents of the cell, or nil." |
|
3437 |
(concat " " contents " |")) |
|
3438 |
|
|
3439 |
|
|
3440 |
;;;; Target |
|
3441 |
|
|
3442 |
(defun org-element-target-parser () |
|
3443 |
"Parse target at point, if any. |
|
3444 |
|
|
3445 |
When at a target, return a list whose car is `target' and cdr |
|
3446 |
a plist with `:begin', `:end', `:value' and `:post-blank' as |
|
3447 |
keywords. Otherwise, return nil. |
|
3448 |
|
|
3449 |
Assume point is at the target." |
|
3450 |
(save-excursion |
|
3451 |
(when (looking-at org-target-regexp) |
|
3452 |
(let ((begin (point)) |
|
3453 |
(value (match-string-no-properties 1)) |
|
3454 |
(post-blank (progn (goto-char (match-end 0)) |
|
3455 |
(skip-chars-forward " \t"))) |
|
3456 |
(end (point))) |
|
3457 |
(list 'target |
|
3458 |
(list :begin begin |
|
3459 |
:end end |
|
3460 |
:value value |
|
3461 |
:post-blank post-blank)))))) |
|
3462 |
|
|
3463 |
(defun org-element-target-interpreter (target _) |
|
3464 |
"Interpret TARGET object as Org syntax." |
|
3465 |
(format "<<%s>>" (org-element-property :value target))) |
|
3466 |
|
|
3467 |
|
|
3468 |
;;;; Timestamp |
|
3469 |
|
|
3470 |
(defconst org-element--timestamp-regexp |
|
3471 |
(concat org-ts-regexp-both |
|
3472 |
"\\|" |
|
3473 |
"\\(?:<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)" |
|
3474 |
"\\|" |
|
3475 |
"\\(?:<%%\\(?:([^>\n]+)\\)>\\)") |
|
3476 |
"Regexp matching any timestamp type object.") |
|
3477 |
|
|
3478 |
(defun org-element-timestamp-parser () |
|
3479 |
"Parse time stamp at point, if any. |
|
3480 |
|
|
3481 |
When at a time stamp, return a list whose car is `timestamp', and |
|
3482 |
cdr a plist with `:type', `:raw-value', `:year-start', |
|
3483 |
`:month-start', `:day-start', `:hour-start', `:minute-start', |
|
3484 |
`:year-end', `:month-end', `:day-end', `:hour-end', |
|
3485 |
`:minute-end', `:repeater-type', `:repeater-value', |
|
3486 |
`:repeater-unit', `:warning-type', `:warning-value', |
|
3487 |
`:warning-unit', `:begin', `:end' and `:post-blank' keywords. |
|
3488 |
Otherwise, return nil. |
|
3489 |
|
|
3490 |
Assume point is at the beginning of the timestamp." |
|
3491 |
(when (looking-at-p org-element--timestamp-regexp) |
|
3492 |
(save-excursion |
|
3493 |
(let* ((begin (point)) |
|
3494 |
(activep (eq (char-after) ?<)) |
|
3495 |
(raw-value |
|
3496 |
(progn |
|
3497 |
(looking-at "\\([<[]\\(%%\\)?.*?\\)[]>]\\(?:--\\([<[].*?[]>]\\)\\)?") |
|
3498 |
(match-string-no-properties 0))) |
|
3499 |
(date-start (match-string-no-properties 1)) |
|
3500 |
(date-end (match-string 3)) |
|
3501 |
(diaryp (match-beginning 2)) |
|
3502 |
(post-blank (progn (goto-char (match-end 0)) |
|
3503 |
(skip-chars-forward " \t"))) |
|
3504 |
(end (point)) |
|
3505 |
(time-range |
|
3506 |
(and (not diaryp) |
|
3507 |
(string-match |
|
3508 |
"[012]?[0-9]:[0-5][0-9]\\(-\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\)" |
|
3509 |
date-start) |
|
3510 |
(cons (string-to-number (match-string 2 date-start)) |
|
3511 |
(string-to-number (match-string 3 date-start))))) |
|
3512 |
(type (cond (diaryp 'diary) |
|
3513 |
((and activep (or date-end time-range)) 'active-range) |
|
3514 |
(activep 'active) |
|
3515 |
((or date-end time-range) 'inactive-range) |
|
3516 |
(t 'inactive))) |
|
3517 |
(repeater-props |
|
3518 |
(and (not diaryp) |
|
3519 |
(string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)" |
|
3520 |
raw-value) |
|
3521 |
(list |
|
3522 |
:repeater-type |
|
3523 |
(let ((type (match-string 1 raw-value))) |
|
3524 |
(cond ((equal "++" type) 'catch-up) |
|
3525 |
((equal ".+" type) 'restart) |
|
3526 |
(t 'cumulate))) |
|
3527 |
:repeater-value (string-to-number (match-string 2 raw-value)) |
|
3528 |
:repeater-unit |
|
3529 |
(pcase (string-to-char (match-string 3 raw-value)) |
|
3530 |
(?h 'hour) (?d 'day) (?w 'week) (?m 'month) (_ 'year))))) |
|
3531 |
(warning-props |
|
3532 |
(and (not diaryp) |
|
3533 |
(string-match "\\(-\\)?-\\([0-9]+\\)\\([hdwmy]\\)" raw-value) |
|
3534 |
(list |
|
3535 |
:warning-type (if (match-string 1 raw-value) 'first 'all) |
|
3536 |
:warning-value (string-to-number (match-string 2 raw-value)) |
|
3537 |
:warning-unit |
|
3538 |
(pcase (string-to-char (match-string 3 raw-value)) |
|
3539 |
(?h 'hour) (?d 'day) (?w 'week) (?m 'month) (_ 'year))))) |
|
3540 |
year-start month-start day-start hour-start minute-start year-end |
|
3541 |
month-end day-end hour-end minute-end) |
|
3542 |
;; Parse date-start. |
|
3543 |
(unless diaryp |
|
3544 |
(let ((date (org-parse-time-string date-start t))) |
|
3545 |
(setq year-start (nth 5 date) |
|
3546 |
month-start (nth 4 date) |
|
3547 |
day-start (nth 3 date) |
|
3548 |
hour-start (nth 2 date) |
|
3549 |
minute-start (nth 1 date)))) |
|
3550 |
;; Compute date-end. It can be provided directly in time-stamp, |
|
3551 |
;; or extracted from time range. Otherwise, it defaults to the |
|
3552 |
;; same values as date-start. |
|
3553 |
(unless diaryp |
|
3554 |
(let ((date (and date-end (org-parse-time-string date-end t)))) |
|
3555 |
(setq year-end (or (nth 5 date) year-start) |
|
3556 |
month-end (or (nth 4 date) month-start) |
|
3557 |
day-end (or (nth 3 date) day-start) |
|
3558 |
hour-end (or (nth 2 date) (car time-range) hour-start) |
|
3559 |
minute-end (or (nth 1 date) (cdr time-range) minute-start)))) |
|
3560 |
(list 'timestamp |
|
3561 |
(nconc (list :type type |
|
3562 |
:raw-value raw-value |
|
3563 |
:year-start year-start |
|
3564 |
:month-start month-start |
|
3565 |
:day-start day-start |
|
3566 |
:hour-start hour-start |
|
3567 |
:minute-start minute-start |
|
3568 |
:year-end year-end |
|
3569 |
:month-end month-end |
|
3570 |
:day-end day-end |
|
3571 |
:hour-end hour-end |
|
3572 |
:minute-end minute-end |
|
3573 |
:begin begin |
|
3574 |
:end end |
|
3575 |
:post-blank post-blank) |
|
3576 |
repeater-props |
|
3577 |
warning-props)))))) |
|
3578 |
|
|
3579 |
(defun org-element-timestamp-interpreter (timestamp _) |
|
3580 |
"Interpret TIMESTAMP object as Org syntax." |
|
3581 |
(let* ((repeat-string |
|
3582 |
(concat |
|
3583 |
(pcase (org-element-property :repeater-type timestamp) |
|
3584 |
(`cumulate "+") (`catch-up "++") (`restart ".+")) |
|
3585 |
(let ((val (org-element-property :repeater-value timestamp))) |
|
3586 |
(and val (number-to-string val))) |
|
3587 |
(pcase (org-element-property :repeater-unit timestamp) |
|
3588 |
(`hour "h") (`day "d") (`week "w") (`month "m") (`year "y")))) |
|
3589 |
(warning-string |
|
3590 |
(concat |
|
3591 |
(pcase (org-element-property :warning-type timestamp) |
|
3592 |
(`first "--") (`all "-")) |
|
3593 |
(let ((val (org-element-property :warning-value timestamp))) |
|
3594 |
(and val (number-to-string val))) |
|
3595 |
(pcase (org-element-property :warning-unit timestamp) |
|
3596 |
(`hour "h") (`day "d") (`week "w") (`month "m") (`year "y")))) |
|
3597 |
(build-ts-string |
|
3598 |
;; Build an Org timestamp string from TIME. ACTIVEP is |
|
3599 |
;; non-nil when time stamp is active. If WITH-TIME-P is |
|
3600 |
;; non-nil, add a time part. HOUR-END and MINUTE-END |
|
3601 |
;; specify a time range in the timestamp. REPEAT-STRING is |
|
3602 |
;; the repeater string, if any. |
|
3603 |
(lambda (time activep &optional with-time-p hour-end minute-end) |
|
3604 |
(let ((ts (format-time-string |
|
3605 |
(funcall (if with-time-p #'cdr #'car) |
|
3606 |
org-time-stamp-formats) |
|
3607 |
time))) |
|
3608 |
(when (and hour-end minute-end) |
|
3609 |
(string-match "[012]?[0-9]:[0-5][0-9]" ts) |
|
3610 |
(setq ts |
|
3611 |
(replace-match |
|
3612 |
(format "\\&-%02d:%02d" hour-end minute-end) |
|
3613 |
nil nil ts))) |
|
3614 |
(unless activep (setq ts (format "[%s]" (substring ts 1 -1)))) |
|
3615 |
(dolist (s (list repeat-string warning-string)) |
|
3616 |
(when (org-string-nw-p s) |
|
3617 |
(setq ts (concat (substring ts 0 -1) |
|
3618 |
" " |
|
3619 |
s |
|
3620 |
(substring ts -1))))) |
|
3621 |
;; Return value. |
|
3622 |
ts))) |
|
3623 |
(type (org-element-property :type timestamp))) |
|
3624 |
(pcase type |
|
3625 |
((or `active `inactive) |
|
3626 |
(let* ((minute-start (org-element-property :minute-start timestamp)) |
|
3627 |
(minute-end (org-element-property :minute-end timestamp)) |
|
3628 |
(hour-start (org-element-property :hour-start timestamp)) |
|
3629 |
(hour-end (org-element-property :hour-end timestamp)) |
|
3630 |
(time-range-p (and hour-start hour-end minute-start minute-end |
|
3631 |
(or (/= hour-start hour-end) |
|
3632 |
(/= minute-start minute-end))))) |
|
3633 |
(funcall |
|
3634 |
build-ts-string |
|
3635 |
(encode-time 0 |
|
3636 |
(or minute-start 0) |
|
3637 |
(or hour-start 0) |
|
3638 |
(org-element-property :day-start timestamp) |
|
3639 |
(org-element-property :month-start timestamp) |
|
3640 |
(org-element-property :year-start timestamp)) |
|
3641 |
(eq type 'active) |
|
3642 |
(and hour-start minute-start) |
|
3643 |
(and time-range-p hour-end) |
|
3644 |
(and time-range-p minute-end)))) |
|
3645 |
((or `active-range `inactive-range) |
|
3646 |
(let ((minute-start (org-element-property :minute-start timestamp)) |
|
3647 |
(minute-end (org-element-property :minute-end timestamp)) |
|
3648 |
(hour-start (org-element-property :hour-start timestamp)) |
|
3649 |
(hour-end (org-element-property :hour-end timestamp))) |
|
3650 |
(concat |
|
3651 |
(funcall |
|
3652 |
build-ts-string (encode-time |
|
3653 |
0 |
|
3654 |
(or minute-start 0) |
|
3655 |
(or hour-start 0) |
|
3656 |
(org-element-property :day-start timestamp) |
|
3657 |
(org-element-property :month-start timestamp) |
|
3658 |
(org-element-property :year-start timestamp)) |
|
3659 |
(eq type 'active-range) |
|
3660 |
(and hour-start minute-start)) |
|
3661 |
"--" |
|
3662 |
(funcall build-ts-string |
|
3663 |
(encode-time 0 |
|
3664 |
(or minute-end 0) |
|
3665 |
(or hour-end 0) |
|
3666 |
(org-element-property :day-end timestamp) |
|
3667 |
(org-element-property :month-end timestamp) |
|
3668 |
(org-element-property :year-end timestamp)) |
|
3669 |
(eq type 'active-range) |
|
3670 |
(and hour-end minute-end))))) |
|
3671 |
(_ (org-element-property :raw-value timestamp))))) |
|
3672 |
|
|
3673 |
|
|
3674 |
;;;; Underline |
|
3675 |
|
|
3676 |
(defun org-element-underline-parser () |
|
3677 |
"Parse underline object at point, if any. |
|
3678 |
|
|
3679 |
When at an underline object, return a list whose car is |
|
3680 |
`underline' and cdr is a plist with `:begin', `:end', |
|
3681 |
`:contents-begin' and `:contents-end' and `:post-blank' keywords. |
|
3682 |
Otherwise, return nil. |
|
3683 |
|
|
3684 |
Assume point is at the first underscore marker." |
|
3685 |
(save-excursion |
|
3686 |
(unless (bolp) (backward-char 1)) |
|
3687 |
(when (looking-at org-emph-re) |
|
3688 |
(let ((begin (match-beginning 2)) |
|
3689 |
(contents-begin (match-beginning 4)) |
|
3690 |
(contents-end (match-end 4)) |
|
3691 |
(post-blank (progn (goto-char (match-end 2)) |
|
3692 |
(skip-chars-forward " \t"))) |
|
3693 |
(end (point))) |
|
3694 |
(list 'underline |
|
3695 |
(list :begin begin |
|
3696 |
:end end |
|
3697 |
:contents-begin contents-begin |
|
3698 |
:contents-end contents-end |
|
3699 |
:post-blank post-blank)))))) |
|
3700 |
|
|
3701 |
(defun org-element-underline-interpreter (_ contents) |
|
3702 |
"Interpret underline object as Org syntax. |
|
3703 |
CONTENTS is the contents of the object." |
|
3704 |
(format "_%s_" contents)) |
|
3705 |
|
|
3706 |
|
|
3707 |
;;;; Verbatim |
|
3708 |
|
|
3709 |
(defun org-element-verbatim-parser () |
|
3710 |
"Parse verbatim object at point, if any. |
|
3711 |
|
|
3712 |
When at a verbatim object, return a list whose car is `verbatim' |
|
3713 |
and cdr is a plist with `:value', `:begin', `:end' and |
|
3714 |
`:post-blank' keywords. Otherwise, return nil. |
|
3715 |
|
|
3716 |
Assume point is at the first equal sign marker." |
|
3717 |
(save-excursion |
|
3718 |
(unless (bolp) (backward-char 1)) |
|
3719 |
(when (looking-at org-verbatim-re) |
|
3720 |
(let ((begin (match-beginning 2)) |
|
3721 |
(value (match-string-no-properties 4)) |
|
3722 |
(post-blank (progn (goto-char (match-end 2)) |
|
3723 |
(skip-chars-forward " \t"))) |
|
3724 |
(end (point))) |
|
3725 |
(list 'verbatim |
|
3726 |
(list :value value |
|
3727 |
:begin begin |
|
3728 |
:end end |
|
3729 |
:post-blank post-blank)))))) |
|
3730 |
|
|
3731 |
(defun org-element-verbatim-interpreter (verbatim _) |
|
3732 |
"Interpret VERBATIM object as Org syntax." |
|
3733 |
(format "=%s=" (org-element-property :value verbatim))) |
|
3734 |
|
|
3735 |
|
|
3736 |
|
|
3737 |
;;; Parsing Element Starting At Point |
|
3738 |
;; |
|
3739 |
;; `org-element--current-element' is the core function of this section. |
|
3740 |
;; It returns the Lisp representation of the element starting at |
|
3741 |
;; point. |
|
3742 |
;; |
|
3743 |
;; `org-element--current-element' makes use of special modes. They |
|
3744 |
;; are activated for fixed element chaining (e.g., `plain-list' > |
|
3745 |
;; `item') or fixed conditional element chaining (e.g., `headline' > |
|
3746 |
;; `section'). Special modes are: `first-section', `item', |
|
3747 |
;; `node-property', `section' and `table-row'. |
|
3748 |
|
|
3749 |
(defun org-element--current-element (limit &optional granularity mode structure) |
|
3750 |
"Parse the element starting at point. |
|
3751 |
|
|
3752 |
Return value is a list like (TYPE PROPS) where TYPE is the type |
|
3753 |
of the element and PROPS a plist of properties associated to the |
|
3754 |
element. |
|
3755 |
|
|
3756 |
Possible types are defined in `org-element-all-elements'. |
|
3757 |
|
|
3758 |
LIMIT bounds the search. |
|
3759 |
|
|
3760 |
Optional argument GRANULARITY determines the depth of the |
|
3761 |
recursion. Allowed values are `headline', `greater-element', |
|
3762 |
`element', `object' or nil. When it is broader than `object' (or |
|
3763 |
nil), secondary values will not be parsed, since they only |
|
3764 |
contain objects. |
|
3765 |
|
|
3766 |
Optional argument MODE, when non-nil, can be either |
|
3767 |
`first-section', `section', `planning', `item', `node-property' |
|
3768 |
and `table-row'. |
|
3769 |
|
|
3770 |
If STRUCTURE isn't provided but MODE is set to `item', it will be |
|
3771 |
computed. |
|
3772 |
|
|
3773 |
This function assumes point is always at the beginning of the |
|
3774 |
element it has to parse." |
|
3775 |
(save-excursion |
|
3776 |
(let ((case-fold-search t) |
|
3777 |
;; Determine if parsing depth allows for secondary strings |
|
3778 |
;; parsing. It only applies to elements referenced in |
|
3779 |
;; `org-element-secondary-value-alist'. |
|
3780 |
(raw-secondary-p (and granularity (not (eq granularity 'object))))) |
|
3781 |
(cond |
|
3782 |
;; Item. |
|
3783 |
((eq mode 'item) |
|
3784 |
(org-element-item-parser limit structure raw-secondary-p)) |
|
3785 |
;; Table Row. |
|
3786 |
((eq mode 'table-row) (org-element-table-row-parser limit)) |
|
3787 |
;; Node Property. |
|
3788 |
((eq mode 'node-property) (org-element-node-property-parser limit)) |
|
3789 |
;; Headline. |
|
3790 |
((org-with-limited-levels (org-at-heading-p)) |
|
3791 |
(org-element-headline-parser limit raw-secondary-p)) |
|
3792 |
;; Sections (must be checked after headline). |
|
3793 |
((eq mode 'section) (org-element-section-parser limit)) |
|
3794 |
((eq mode 'first-section) |
|
3795 |
(org-element-section-parser |
|
3796 |
(or (save-excursion (org-with-limited-levels (outline-next-heading))) |
|
3797 |
limit))) |
|
3798 |
;; Planning. |
|
3799 |
((and (eq mode 'planning) |
|
3800 |
(eq ?* (char-after (line-beginning-position 0))) |
|
3801 |
(looking-at org-planning-line-re)) |
|
3802 |
(org-element-planning-parser limit)) |
|
3803 |
;; Property drawer. |
|
3804 |
((and (memq mode '(planning property-drawer)) |
|
3805 |
(eq ?* (char-after (line-beginning-position |
|
3806 |
(if (eq mode 'planning) 0 -1)))) |
|
3807 |
(looking-at org-property-drawer-re)) |
|
3808 |
(org-element-property-drawer-parser limit)) |
|
3809 |
;; When not at bol, point is at the beginning of an item or |
|
3810 |
;; a footnote definition: next item is always a paragraph. |
|
3811 |
((not (bolp)) (org-element-paragraph-parser limit (list (point)))) |
|
3812 |
;; Clock. |
|
3813 |
((looking-at org-clock-line-re) (org-element-clock-parser limit)) |
|
3814 |
;; Inlinetask. |
|
3815 |
((org-at-heading-p) |
|
3816 |
(org-element-inlinetask-parser limit raw-secondary-p)) |
|
3817 |
;; From there, elements can have affiliated keywords. |
|
3818 |
(t (let ((affiliated (org-element--collect-affiliated-keywords limit))) |
|
3819 |
(cond |
|
3820 |
;; Jumping over affiliated keywords put point off-limits. |
|
3821 |
;; Parse them as regular keywords. |
|
3822 |
((and (cdr affiliated) (>= (point) limit)) |
|
3823 |
(goto-char (car affiliated)) |
|
3824 |
(org-element-keyword-parser limit nil)) |
|
3825 |
;; LaTeX Environment. |
|
3826 |
((looking-at org-element--latex-begin-environment) |
|
3827 |
(org-element-latex-environment-parser limit affiliated)) |
|
3828 |
;; Drawer and Property Drawer. |
|
3829 |
((looking-at org-drawer-regexp) |
|
3830 |
(org-element-drawer-parser limit affiliated)) |
|
3831 |
;; Fixed Width |
|
3832 |
((looking-at "[ \t]*:\\( \\|$\\)") |
|
3833 |
(org-element-fixed-width-parser limit affiliated)) |
|
3834 |
;; Inline Comments, Blocks, Babel Calls, Dynamic Blocks and |
|
3835 |
;; Keywords. |
|
3836 |
((looking-at "[ \t]*#") |
|
3837 |
(goto-char (match-end 0)) |
|
3838 |
(cond |
|
3839 |
((looking-at "\\(?: \\|$\\)") |
|
3840 |
(beginning-of-line) |
|
3841 |
(org-element-comment-parser limit affiliated)) |
|
3842 |
((looking-at "\\+BEGIN_\\(\\S-+\\)") |
|
3843 |
(beginning-of-line) |
|
3844 |
(funcall (pcase (upcase (match-string 1)) |
|
3845 |
("CENTER" #'org-element-center-block-parser) |
|
3846 |
("COMMENT" #'org-element-comment-block-parser) |
|
3847 |
("EXAMPLE" #'org-element-example-block-parser) |
|
3848 |
("EXPORT" #'org-element-export-block-parser) |
|
3849 |
("QUOTE" #'org-element-quote-block-parser) |
|
3850 |
("SRC" #'org-element-src-block-parser) |
|
3851 |
("VERSE" #'org-element-verse-block-parser) |
|
3852 |
(_ #'org-element-special-block-parser)) |
|
3853 |
limit |
|
3854 |
affiliated)) |
|
3855 |
((looking-at "\\+CALL:") |
|
3856 |
(beginning-of-line) |
|
3857 |
(org-element-babel-call-parser limit affiliated)) |
|
3858 |
((looking-at "\\+BEGIN:? ") |
|
3859 |
(beginning-of-line) |
|
3860 |
(org-element-dynamic-block-parser limit affiliated)) |
|
3861 |
((looking-at "\\+\\S-+:") |
|
3862 |
(beginning-of-line) |
|
3863 |
(org-element-keyword-parser limit affiliated)) |
|
3864 |
(t |
|
3865 |
(beginning-of-line) |
|
3866 |
(org-element-paragraph-parser limit affiliated)))) |
|
3867 |
;; Footnote Definition. |
|
3868 |
((looking-at org-footnote-definition-re) |
|
3869 |
(org-element-footnote-definition-parser limit affiliated)) |
|
3870 |
;; Horizontal Rule. |
|
3871 |
((looking-at "[ \t]*-\\{5,\\}[ \t]*$") |
|
3872 |
(org-element-horizontal-rule-parser limit affiliated)) |
|
3873 |
;; Diary Sexp. |
|
3874 |
((looking-at "%%(") |
|
3875 |
(org-element-diary-sexp-parser limit affiliated)) |
|
3876 |
;; Table. |
|
3877 |
((looking-at "[ \t]*\\(|\\|\\+\\(-+\\+\\)+[ \t]*$\\)") |
|
3878 |
(org-element-table-parser limit affiliated)) |
|
3879 |
;; List. |
|
3880 |
((looking-at (org-item-re)) |
|
3881 |
(org-element-plain-list-parser |
|
3882 |
limit affiliated |
|
3883 |
(or structure (org-element--list-struct limit)))) |
|
3884 |
;; Default element: Paragraph. |
|
3885 |
(t (org-element-paragraph-parser limit affiliated))))))))) |
|
3886 |
|
|
3887 |
|
|
3888 |
;; Most elements can have affiliated keywords. When looking for an |
|
3889 |
;; element beginning, we want to move before them, as they belong to |
|
3890 |
;; that element, and, in the meantime, collect information they give |
|
3891 |
;; into appropriate properties. Hence the following function. |
|
3892 |
|
|
3893 |
(defun org-element--collect-affiliated-keywords (limit) |
|
3894 |
"Collect affiliated keywords from point down to LIMIT. |
|
3895 |
|
|
3896 |
Return a list whose CAR is the position at the first of them and |
|
3897 |
CDR a plist of keywords and values and move point to the |
|
3898 |
beginning of the first line after them. |
|
3899 |
|
|
3900 |
As a special case, if element doesn't start at the beginning of |
|
3901 |
the line (e.g., a paragraph starting an item), CAR is current |
|
3902 |
position of point and CDR is nil." |
|
3903 |
(if (not (bolp)) (list (point)) |
|
3904 |
(let ((case-fold-search t) |
|
3905 |
(origin (point)) |
|
3906 |
;; RESTRICT is the list of objects allowed in parsed |
|
3907 |
;; keywords value. |
|
3908 |
(restrict (org-element-restriction 'keyword)) |
|
3909 |
output) |
|
3910 |
(while (and (< (point) limit) (looking-at org-element--affiliated-re)) |
|
3911 |
(let* ((raw-kwd (upcase (match-string 1))) |
|
3912 |
;; Apply translation to RAW-KWD. From there, KWD is |
|
3913 |
;; the official keyword. |
|
3914 |
(kwd (or (cdr (assoc raw-kwd |
|
3915 |
org-element-keyword-translation-alist)) |
|
3916 |
raw-kwd)) |
|
3917 |
;; Find main value for any keyword. |
|
3918 |
(value |
|
3919 |
(save-match-data |
|
3920 |
(org-trim |
|
3921 |
(buffer-substring-no-properties |
|
3922 |
(match-end 0) (line-end-position))))) |
|
3923 |
;; PARSEDP is non-nil when keyword should have its |
|
3924 |
;; value parsed. |
|
3925 |
(parsedp (member kwd org-element-parsed-keywords)) |
|
3926 |
;; If KWD is a dual keyword, find its secondary |
|
3927 |
;; value. Maybe parse it. |
|
3928 |
(dualp (member kwd org-element-dual-keywords)) |
|
3929 |
(dual-value |
|
3930 |
(and dualp |
|
3931 |
(let ((sec (match-string-no-properties 2))) |
|
3932 |
(if (or (not sec) (not parsedp)) sec |
|
3933 |
(save-match-data |
|
3934 |
(org-element--parse-objects |
|
3935 |
(match-beginning 2) (match-end 2) nil restrict)))))) |
|
3936 |
;; Attribute a property name to KWD. |
|
3937 |
(kwd-sym (and kwd (intern (concat ":" (downcase kwd)))))) |
|
3938 |
;; Now set final shape for VALUE. |
|
3939 |
(when parsedp |
|
3940 |
(setq value |
|
3941 |
(org-element--parse-objects |
|
3942 |
(match-end 0) |
|
3943 |
(progn (end-of-line) (skip-chars-backward " \t") (point)) |
|
3944 |
nil restrict))) |
|
3945 |
(when dualp |
|
3946 |
(setq value (and (or value dual-value) (cons value dual-value)))) |
|
3947 |
(when (or (member kwd org-element-multiple-keywords) |
|
3948 |
;; Attributes can always appear on multiple lines. |
|
3949 |
(string-match "^ATTR_" kwd)) |
|
3950 |
(setq value (cons value (plist-get output kwd-sym)))) |
|
3951 |
;; Eventually store the new value in OUTPUT. |
|
3952 |
(setq output (plist-put output kwd-sym value)) |
|
3953 |
;; Move to next keyword. |
|
3954 |
(forward-line))) |
|
3955 |
;; If affiliated keywords are orphaned: move back to first one. |
|
3956 |
;; They will be parsed as a paragraph. |
|
3957 |
(when (looking-at "[ \t]*$") (goto-char origin) (setq output nil)) |
|
3958 |
;; Return value. |
|
3959 |
(cons origin output)))) |
|
3960 |
|
|
3961 |
|
|
3962 |
|
|
3963 |
;;; The Org Parser |
|
3964 |
;; |
|
3965 |
;; The two major functions here are `org-element-parse-buffer', which |
|
3966 |
;; parses Org syntax inside the current buffer, taking into account |
|
3967 |
;; region, narrowing, or even visibility if specified, and |
|
3968 |
;; `org-element-parse-secondary-string', which parses objects within |
|
3969 |
;; a given string. |
|
3970 |
;; |
|
3971 |
;; The (almost) almighty `org-element-map' allows applying a function |
|
3972 |
;; on elements or objects matching some type, and accumulating the |
|
3973 |
;; resulting values. In an export situation, it also skips unneeded |
|
3974 |
;; parts of the parse tree. |
|
3975 |
|
|
3976 |
(defun org-element-parse-buffer (&optional granularity visible-only) |
|
3977 |
"Recursively parse the buffer and return structure. |
|
3978 |
If narrowing is in effect, only parse the visible part of the |
|
3979 |
buffer. |
|
3980 |
|
|
3981 |
Optional argument GRANULARITY determines the depth of the |
|
3982 |
recursion. It can be set to the following symbols: |
|
3983 |
|
|
3984 |
`headline' Only parse headlines. |
|
3985 |
`greater-element' Don't recurse into greater elements except |
|
3986 |
headlines and sections. Thus, elements |
|
3987 |
parsed are the top-level ones. |
|
3988 |
`element' Parse everything but objects and plain text. |
|
3989 |
`object' Parse the complete buffer (default). |
|
3990 |
|
|
3991 |
When VISIBLE-ONLY is non-nil, don't parse contents of hidden |
|
3992 |
elements. |
|
3993 |
|
|
3994 |
An element or object is represented as a list with the |
|
3995 |
pattern (TYPE PROPERTIES CONTENTS), where : |
|
3996 |
|
|
3997 |
TYPE is a symbol describing the element or object. See |
|
3998 |
`org-element-all-elements' and `org-element-all-objects' for an |
|
3999 |
exhaustive list of such symbols. One can retrieve it with |
|
4000 |
`org-element-type' function. |
|
4001 |
|
|
4002 |
PROPERTIES is the list of attributes attached to the element or |
|
4003 |
object, as a plist. Although most of them are specific to the |
|
4004 |
element or object type, all types share `:begin', `:end', |
|
4005 |
`:post-blank' and `:parent' properties, which respectively |
|
4006 |
refer to buffer position where the element or object starts, |
|
4007 |
ends, the number of white spaces or blank lines after it, and |
|
4008 |
the element or object containing it. Properties values can be |
|
4009 |
obtained by using `org-element-property' function. |
|
4010 |
|
|
4011 |
CONTENTS is a list of elements, objects or raw strings |
|
4012 |
contained in the current element or object, when applicable. |
|
4013 |
One can access them with `org-element-contents' function. |
|
4014 |
|
|
4015 |
The Org buffer has `org-data' as type and nil as properties. |
|
4016 |
`org-element-map' function can be used to find specific elements |
|
4017 |
or objects within the parse tree. |
|
4018 |
|
|
4019 |
This function assumes that current major mode is `org-mode'." |
|
4020 |
(save-excursion |
|
4021 |
(goto-char (point-min)) |
|
4022 |
(org-skip-whitespace) |
|
4023 |
(org-element--parse-elements |
|
4024 |
(point-at-bol) (point-max) |
|
4025 |
;; Start in `first-section' mode so text before the first |
|
4026 |
;; headline belongs to a section. |
|
4027 |
'first-section nil granularity visible-only (list 'org-data nil)))) |
|
4028 |
|
|
4029 |
(defun org-element-parse-secondary-string (string restriction &optional parent) |
|
4030 |
"Recursively parse objects in STRING and return structure. |
|
4031 |
|
|
4032 |
RESTRICTION is a symbol limiting the object types that will be |
|
4033 |
looked after. |
|
4034 |
|
|
4035 |
Optional argument PARENT, when non-nil, is the element or object |
|
4036 |
containing the secondary string. It is used to set correctly |
|
4037 |
`:parent' property within the string. |
|
4038 |
|
|
4039 |
If STRING is the empty string or nil, return nil." |
|
4040 |
(cond |
|
4041 |
((not string) nil) |
|
4042 |
((equal string "") nil) |
|
4043 |
(t (let ((local-variables (buffer-local-variables))) |
|
4044 |
(with-temp-buffer |
|
4045 |
(dolist (v local-variables) |
|
4046 |
(ignore-errors |
|
4047 |
(if (symbolp v) (makunbound v) |
|
4048 |
(set (make-local-variable (car v)) (cdr v))))) |
|
4049 |
(insert string) |
|
4050 |
(restore-buffer-modified-p nil) |
|
4051 |
(org-element--parse-objects |
|
4052 |
(point-min) (point-max) nil restriction parent)))))) |
|
4053 |
|
|
4054 |
(defun org-element-map |
|
4055 |
(data types fun &optional info first-match no-recursion with-affiliated) |
|
4056 |
"Map a function on selected elements or objects. |
|
4057 |
|
|
4058 |
DATA is a parse tree, an element, an object, a string, or a list |
|
4059 |
of such constructs. TYPES is a symbol or list of symbols of |
|
4060 |
elements or objects types (see `org-element-all-elements' and |
|
4061 |
`org-element-all-objects' for a complete list of types). FUN is |
|
4062 |
the function called on the matching element or object. It has to |
|
4063 |
accept one argument: the element or object itself. |
|
4064 |
|
|
4065 |
When optional argument INFO is non-nil, it should be a plist |
|
4066 |
holding export options. In that case, parts of the parse tree |
|
4067 |
not exportable according to that property list will be skipped. |
|
4068 |
|
|
4069 |
When optional argument FIRST-MATCH is non-nil, stop at the first |
|
4070 |
match for which FUN doesn't return nil, and return that value. |
|
4071 |
|
|
4072 |
Optional argument NO-RECURSION is a symbol or a list of symbols |
|
4073 |
representing elements or objects types. `org-element-map' won't |
|
4074 |
enter any recursive element or object whose type belongs to that |
|
4075 |
list. Though, FUN can still be applied on them. |
|
4076 |
|
|
4077 |
When optional argument WITH-AFFILIATED is non-nil, FUN will also |
|
4078 |
apply to matching objects within parsed affiliated keywords (see |
|
4079 |
`org-element-parsed-keywords'). |
|
4080 |
|
|
4081 |
Nil values returned from FUN do not appear in the results. |
|
4082 |
|
|
4083 |
|
|
4084 |
Examples: |
|
4085 |
--------- |
|
4086 |
|
|
4087 |
Assuming TREE is a variable containing an Org buffer parse tree, |
|
4088 |
the following example will return a flat list of all `src-block' |
|
4089 |
and `example-block' elements in it: |
|
4090 |
|
|
4091 |
(org-element-map tree \\='(example-block src-block) #\\='identity) |
|
4092 |
|
|
4093 |
The following snippet will find the first headline with a level |
|
4094 |
of 1 and a \"phone\" tag, and will return its beginning position: |
|
4095 |
|
|
4096 |
(org-element-map tree \\='headline |
|
4097 |
(lambda (hl) |
|
4098 |
(and (= (org-element-property :level hl) 1) |
|
4099 |
(member \"phone\" (org-element-property :tags hl)) |
|
4100 |
(org-element-property :begin hl))) |
|
4101 |
nil t) |
|
4102 |
|
|
4103 |
The next example will return a flat list of all `plain-list' type |
|
4104 |
elements in TREE that are not a sub-list themselves: |
|
4105 |
|
|
4106 |
(org-element-map tree \\='plain-list #\\='identity nil nil \\='plain-list) |
|
4107 |
|
|
4108 |
Eventually, this example will return a flat list of all `bold' |
|
4109 |
type objects containing a `latex-snippet' type object, even |
|
4110 |
looking into captions: |
|
4111 |
|
|
4112 |
(org-element-map tree \\='bold |
|
4113 |
(lambda (b) |
|
4114 |
(and (org-element-map b \\='latex-snippet #\\='identity nil t) b)) |
|
4115 |
nil nil nil t)" |
|
4116 |
;; Ensure TYPES and NO-RECURSION are a list, even of one element. |
|
4117 |
(let* ((types (if (listp types) types (list types))) |
|
4118 |
(no-recursion (if (listp no-recursion) no-recursion |
|
4119 |
(list no-recursion))) |
|
4120 |
;; Recursion depth is determined by --CATEGORY. |
|
4121 |
(--category |
|
4122 |
(catch :--found |
|
4123 |
(let ((category 'greater-elements) |
|
4124 |
(all-objects (cons 'plain-text org-element-all-objects))) |
|
4125 |
(dolist (type types category) |
|
4126 |
(cond ((memq type all-objects) |
|
4127 |
;; If one object is found, the function has |
|
4128 |
;; to recurse into every object. |
|
4129 |
(throw :--found 'objects)) |
|
4130 |
((not (memq type org-element-greater-elements)) |
|
4131 |
;; If one regular element is found, the |
|
4132 |
;; function has to recurse, at least, into |
|
4133 |
;; every element it encounters. |
|
4134 |
(and (not (eq category 'elements)) |
|
4135 |
(setq category 'elements)))))))) |
|
4136 |
--acc) |
|
4137 |
(letrec ((--walk-tree |
|
4138 |
(lambda (--data) |
|
4139 |
;; Recursively walk DATA. INFO, if non-nil, is a plist |
|
4140 |
;; holding contextual information. |
|
4141 |
(let ((--type (org-element-type --data))) |
|
4142 |
(cond |
|
4143 |
((not --data)) |
|
4144 |
;; Ignored element in an export context. |
|
4145 |
((and info (memq --data (plist-get info :ignore-list)))) |
|
4146 |
;; List of elements or objects. |
|
4147 |
((not --type) (mapc --walk-tree --data)) |
|
4148 |
;; Unconditionally enter parse trees. |
|
4149 |
((eq --type 'org-data) |
|
4150 |
(mapc --walk-tree (org-element-contents --data))) |
|
4151 |
(t |
|
4152 |
;; Check if TYPE is matching among TYPES. If so, |
|
4153 |
;; apply FUN to --DATA and accumulate return value |
|
4154 |
;; into --ACC (or exit if FIRST-MATCH is non-nil). |
|
4155 |
(when (memq --type types) |
|
4156 |
(let ((result (funcall fun --data))) |
|
4157 |
(cond ((not result)) |
|
4158 |
(first-match (throw :--map-first-match result)) |
|
4159 |
(t (push result --acc))))) |
|
4160 |
;; If --DATA has a secondary string that can contain |
|
4161 |
;; objects with their type among TYPES, look inside. |
|
4162 |
(when (and (eq --category 'objects) (not (stringp --data))) |
|
4163 |
(dolist (p (cdr (assq --type |
|
4164 |
org-element-secondary-value-alist))) |
|
4165 |
(funcall --walk-tree (org-element-property p --data)))) |
|
4166 |
;; If --DATA has any parsed affiliated keywords and |
|
4167 |
;; WITH-AFFILIATED is non-nil, look for objects in |
|
4168 |
;; them. |
|
4169 |
(when (and with-affiliated |
|
4170 |
(eq --category 'objects) |
|
4171 |
(eq (org-element-class --data) 'element)) |
|
4172 |
(dolist (kwd-pair org-element--parsed-properties-alist) |
|
4173 |
(let ((kwd (car kwd-pair)) |
|
4174 |
(value (org-element-property (cdr kwd-pair) --data))) |
|
4175 |
;; Pay attention to the type of parsed |
|
4176 |
;; keyword. In particular, preserve order for |
|
4177 |
;; multiple keywords. |
|
4178 |
(cond |
|
4179 |
((not value)) |
|
4180 |
((member kwd org-element-dual-keywords) |
|
4181 |
(if (member kwd org-element-multiple-keywords) |
|
4182 |
(dolist (line (reverse value)) |
|
4183 |
(funcall --walk-tree (cdr line)) |
|
4184 |
(funcall --walk-tree (car line))) |
|
4185 |
(funcall --walk-tree (cdr value)) |
|
4186 |
(funcall --walk-tree (car value)))) |
|
4187 |
((member kwd org-element-multiple-keywords) |
|
4188 |
(mapc --walk-tree (reverse value))) |
|
4189 |
(t (funcall --walk-tree value)))))) |
|
4190 |
;; Determine if a recursion into --DATA is possible. |
|
4191 |
(cond |
|
4192 |
;; --TYPE is explicitly removed from recursion. |
|
4193 |
((memq --type no-recursion)) |
|
4194 |
;; --DATA has no contents. |
|
4195 |
((not (org-element-contents --data))) |
|
4196 |
;; Looking for greater elements but --DATA is |
|
4197 |
;; simply an element or an object. |
|
4198 |
((and (eq --category 'greater-elements) |
|
4199 |
(not (memq --type org-element-greater-elements)))) |
|
4200 |
;; Looking for elements but --DATA is an object. |
|
4201 |
((and (eq --category 'elements) |
|
4202 |
(eq (org-element-class --data) 'object))) |
|
4203 |
;; In any other case, map contents. |
|
4204 |
(t (mapc --walk-tree (org-element-contents --data)))))))))) |
|
4205 |
(catch :--map-first-match |
|
4206 |
(funcall --walk-tree data) |
|
4207 |
;; Return value in a proper order. |
|
4208 |
(nreverse --acc))))) |
|
4209 |
(put 'org-element-map 'lisp-indent-function 2) |
|
4210 |
|
|
4211 |
;; The following functions are internal parts of the parser. |
|
4212 |
;; |
|
4213 |
;; The first one, `org-element--parse-elements' acts at the element's |
|
4214 |
;; level. |
|
4215 |
;; |
|
4216 |
;; The second one, `org-element--parse-objects' applies on all objects |
|
4217 |
;; of a paragraph or a secondary string. It calls |
|
4218 |
;; `org-element--object-lex' to find the next object in the current |
|
4219 |
;; container. |
|
4220 |
|
|
4221 |
(defsubst org-element--next-mode (type parentp) |
|
4222 |
"Return next special mode according to TYPE, or nil. |
|
4223 |
TYPE is a symbol representing the type of an element or object |
|
4224 |
containing next element if PARENTP is non-nil, or before it |
|
4225 |
otherwise. Modes can be either `first-section', `item', |
|
4226 |
`node-property', `planning', `property-drawer', `section', |
|
4227 |
`table-row' or nil." |
|
4228 |
(if parentp |
|
4229 |
(pcase type |
|
4230 |
(`headline 'section) |
|
4231 |
(`inlinetask 'planning) |
|
4232 |
(`plain-list 'item) |
|
4233 |
(`property-drawer 'node-property) |
|
4234 |
(`section 'planning) |
|
4235 |
(`table 'table-row)) |
|
4236 |
(pcase type |
|
4237 |
(`item 'item) |
|
4238 |
(`node-property 'node-property) |
|
4239 |
(`planning 'property-drawer) |
|
4240 |
(`table-row 'table-row)))) |
|
4241 |
|
|
4242 |
(defun org-element--parse-elements |
|
4243 |
(beg end mode structure granularity visible-only acc) |
|
4244 |
"Parse elements between BEG and END positions. |
|
4245 |
|
|
4246 |
MODE prioritizes some elements over the others. It can be set to |
|
4247 |
`first-section', `section', `planning', `item', `node-property' |
|
4248 |
or `table-row'. |
|
4249 |
|
|
4250 |
When value is `item', STRUCTURE will be used as the current list |
|
4251 |
structure. |
|
4252 |
|
|
4253 |
GRANULARITY determines the depth of the recursion. See |
|
4254 |
`org-element-parse-buffer' for more information. |
|
4255 |
|
|
4256 |
When VISIBLE-ONLY is non-nil, don't parse contents of hidden |
|
4257 |
elements. |
|
4258 |
|
|
4259 |
Elements are accumulated into ACC." |
|
4260 |
(save-excursion |
|
4261 |
(goto-char beg) |
|
4262 |
;; Visible only: skip invisible parts at the beginning of the |
|
4263 |
;; element. |
|
4264 |
(when (and visible-only (org-invisible-p2)) |
|
4265 |
(goto-char (min (1+ (org-find-visible)) end))) |
|
4266 |
;; When parsing only headlines, skip any text before first one. |
|
4267 |
(when (and (eq granularity 'headline) (not (org-at-heading-p))) |
|
4268 |
(org-with-limited-levels (outline-next-heading))) |
|
4269 |
(let (elements) |
|
4270 |
(while (< (point) end) |
|
4271 |
;; Find current element's type and parse it accordingly to |
|
4272 |
;; its category. |
|
4273 |
(let* ((element (org-element--current-element |
|
4274 |
end granularity mode structure)) |
|
4275 |
(type (org-element-type element)) |
|
4276 |
(cbeg (org-element-property :contents-begin element))) |
|
4277 |
(goto-char (org-element-property :end element)) |
|
4278 |
;; Visible only: skip invisible parts between siblings. |
|
4279 |
(when (and visible-only (org-invisible-p2)) |
|
4280 |
(goto-char (min (1+ (org-find-visible)) end))) |
|
4281 |
;; Fill ELEMENT contents by side-effect. |
|
4282 |
(cond |
|
4283 |
;; If element has no contents, don't modify it. |
|
4284 |
((not cbeg)) |
|
4285 |
;; Greater element: parse it between `contents-begin' and |
|
4286 |
;; `contents-end'. Make sure GRANULARITY allows the |
|
4287 |
;; recursion, or ELEMENT is a headline, in which case going |
|
4288 |
;; inside is mandatory, in order to get sub-level headings. |
|
4289 |
((and (memq type org-element-greater-elements) |
|
4290 |
(or (memq granularity '(element object nil)) |
|
4291 |
(and (eq granularity 'greater-element) |
|
4292 |
(eq type 'section)) |
|
4293 |
(eq type 'headline))) |
|
4294 |
(org-element--parse-elements |
|
4295 |
cbeg (org-element-property :contents-end element) |
|
4296 |
;; Possibly switch to a special mode. |
|
4297 |
(org-element--next-mode type t) |
|
4298 |
(and (memq type '(item plain-list)) |
|
4299 |
(org-element-property :structure element)) |
|
4300 |
granularity visible-only element)) |
|
4301 |
;; ELEMENT has contents. Parse objects inside, if |
|
4302 |
;; GRANULARITY allows it. |
|
4303 |
((memq granularity '(object nil)) |
|
4304 |
(org-element--parse-objects |
|
4305 |
cbeg (org-element-property :contents-end element) element |
|
4306 |
(org-element-restriction type)))) |
|
4307 |
(push (org-element-put-property element :parent acc) elements) |
|
4308 |
;; Update mode. |
|
4309 |
(setq mode (org-element--next-mode type nil)))) |
|
4310 |
;; Return result. |
|
4311 |
(apply #'org-element-set-contents acc (nreverse elements))))) |
|
4312 |
|
|
4313 |
(defun org-element--object-lex (restriction) |
|
4314 |
"Return next object in current buffer or nil. |
|
4315 |
RESTRICTION is a list of object types, as symbols, that should be |
|
4316 |
looked after. This function assumes that the buffer is narrowed |
|
4317 |
to an appropriate container (e.g., a paragraph)." |
|
4318 |
(if (memq 'table-cell restriction) (org-element-table-cell-parser) |
|
4319 |
(let* ((start (point)) |
|
4320 |
(limit |
|
4321 |
;; Object regexp sometimes needs to have a peek at |
|
4322 |
;; a character ahead. Therefore, when there is a hard |
|
4323 |
;; limit, make it one more than the true beginning of the |
|
4324 |
;; radio target. |
|
4325 |
(save-excursion |
|
4326 |
(cond ((not org-target-link-regexp) nil) |
|
4327 |
((not (memq 'link restriction)) nil) |
|
4328 |
((progn |
|
4329 |
(unless (bolp) (forward-char -1)) |
|
4330 |
(not (re-search-forward org-target-link-regexp nil t))) |
|
4331 |
nil) |
|
4332 |
;; Since we moved backward, we do not want to |
|
4333 |
;; match again an hypothetical 1-character long |
|
4334 |
;; radio link before us. Realizing that this can |
|
4335 |
;; only happen if such a radio link starts at |
|
4336 |
;; beginning of line, we prevent this here. |
|
4337 |
((and (= start (1+ (line-beginning-position))) |
|
4338 |
(= start (match-end 1))) |
|
4339 |
(and (re-search-forward org-target-link-regexp nil t) |
|
4340 |
(1+ (match-beginning 1)))) |
|
4341 |
(t (1+ (match-beginning 1)))))) |
|
4342 |
found) |
|
4343 |
(save-excursion |
|
4344 |
(while (and (not found) |
|
4345 |
(re-search-forward org-element--object-regexp limit 'move)) |
|
4346 |
(goto-char (match-beginning 0)) |
|
4347 |
(let ((result (match-string 0))) |
|
4348 |
(setq found |
|
4349 |
(cond |
|
4350 |
((string-prefix-p "call_" result t) |
|
4351 |
(and (memq 'inline-babel-call restriction) |
|
4352 |
(org-element-inline-babel-call-parser))) |
|
4353 |
((string-prefix-p "src_" result t) |
|
4354 |
(and (memq 'inline-src-block restriction) |
|
4355 |
(org-element-inline-src-block-parser))) |
|
4356 |
(t |
|
4357 |
(pcase (char-after) |
|
4358 |
(?^ (and (memq 'superscript restriction) |
|
4359 |
(org-element-superscript-parser))) |
|
4360 |
(?_ (or (and (memq 'subscript restriction) |
|
4361 |
(org-element-subscript-parser)) |
|
4362 |
(and (memq 'underline restriction) |
|
4363 |
(org-element-underline-parser)))) |
|
4364 |
(?* (and (memq 'bold restriction) |
|
4365 |
(org-element-bold-parser))) |
|
4366 |
(?/ (and (memq 'italic restriction) |
|
4367 |
(org-element-italic-parser))) |
|
4368 |
(?~ (and (memq 'code restriction) |
|
4369 |
(org-element-code-parser))) |
|
4370 |
(?= (and (memq 'verbatim restriction) |
|
4371 |
(org-element-verbatim-parser))) |
|
4372 |
(?+ (and (memq 'strike-through restriction) |
|
4373 |
(org-element-strike-through-parser))) |
|
4374 |
(?@ (and (memq 'export-snippet restriction) |
|
4375 |
(org-element-export-snippet-parser))) |
|
4376 |
(?{ (and (memq 'macro restriction) |
|
4377 |
(org-element-macro-parser))) |
|
4378 |
(?$ (and (memq 'latex-fragment restriction) |
|
4379 |
(org-element-latex-fragment-parser))) |
|
4380 |
(?< |
|
4381 |
(if (eq (aref result 1) ?<) |
|
4382 |
(or (and (memq 'radio-target restriction) |
|
4383 |
(org-element-radio-target-parser)) |
|
4384 |
(and (memq 'target restriction) |
|
4385 |
(org-element-target-parser))) |
|
4386 |
(or (and (memq 'timestamp restriction) |
|
4387 |
(org-element-timestamp-parser)) |
|
4388 |
(and (memq 'link restriction) |
|
4389 |
(org-element-link-parser))))) |
|
4390 |
(?\\ |
|
4391 |
(if (eq (aref result 1) ?\\) |
|
4392 |
(and (memq 'line-break restriction) |
|
4393 |
(org-element-line-break-parser)) |
|
4394 |
(or (and (memq 'entity restriction) |
|
4395 |
(org-element-entity-parser)) |
|
4396 |
(and (memq 'latex-fragment restriction) |
|
4397 |
(org-element-latex-fragment-parser))))) |
|
4398 |
(?\[ |
|
4399 |
(if (eq (aref result 1) ?\[) |
|
4400 |
(and (memq 'link restriction) |
|
4401 |
(org-element-link-parser)) |
|
4402 |
(or (and (memq 'footnote-reference restriction) |
|
4403 |
(org-element-footnote-reference-parser)) |
|
4404 |
(and (memq 'timestamp restriction) |
|
4405 |
(org-element-timestamp-parser)) |
|
4406 |
(and (memq 'statistics-cookie restriction) |
|
4407 |
(org-element-statistics-cookie-parser))))) |
|
4408 |
;; This is probably a plain link. |
|
4409 |
(_ (and (memq 'link restriction) |
|
4410 |
(org-element-link-parser))))))) |
|
4411 |
(or (eobp) (forward-char)))) |
|
4412 |
(cond (found) |
|
4413 |
(limit (forward-char -1) |
|
4414 |
(org-element-link-parser)) ;radio link |
|
4415 |
(t nil)))))) |
|
4416 |
|
|
4417 |
(defun org-element--parse-objects (beg end acc restriction &optional parent) |
|
4418 |
"Parse objects between BEG and END and return recursive structure. |
|
4419 |
|
|
4420 |
Objects are accumulated in ACC. RESTRICTION is a list of object |
|
4421 |
successors which are allowed in the current object. |
|
4422 |
|
|
4423 |
ACC becomes the parent for all parsed objects. However, if ACC |
|
4424 |
is nil (i.e., a secondary string is being parsed) and optional |
|
4425 |
argument PARENT is non-nil, use it as the parent for all objects. |
|
4426 |
Eventually, if both ACC and PARENT are nil, the common parent is |
|
4427 |
the list of objects itself." |
|
4428 |
(save-excursion |
|
4429 |
(save-restriction |
|
4430 |
(narrow-to-region beg end) |
|
4431 |
(goto-char (point-min)) |
|
4432 |
(let (next-object contents) |
|
4433 |
(while (and (not (eobp)) |
|
4434 |
(setq next-object (org-element--object-lex restriction))) |
|
4435 |
;; Text before any object. |
|
4436 |
(let ((obj-beg (org-element-property :begin next-object))) |
|
4437 |
(unless (= (point) obj-beg) |
|
4438 |
(let ((text (buffer-substring-no-properties (point) obj-beg))) |
|
4439 |
(push (if acc (org-element-put-property text :parent acc) text) |
|
4440 |
contents)))) |
|
4441 |
;; Object... |
|
4442 |
(let ((obj-end (org-element-property :end next-object)) |
|
4443 |
(cont-beg (org-element-property :contents-begin next-object))) |
|
4444 |
(when acc (org-element-put-property next-object :parent acc)) |
|
4445 |
(push (if cont-beg |
|
4446 |
;; Fill contents of NEXT-OBJECT if possible. |
|
4447 |
(org-element--parse-objects |
|
4448 |
cont-beg |
|
4449 |
(org-element-property :contents-end next-object) |
|
4450 |
next-object |
|
4451 |
(org-element-restriction next-object)) |
|
4452 |
next-object) |
|
4453 |
contents) |
|
4454 |
(goto-char obj-end))) |
|
4455 |
;; Text after last object. |
|
4456 |
(unless (eobp) |
|
4457 |
(let ((text (buffer-substring-no-properties (point) end))) |
|
4458 |
(push (if acc (org-element-put-property text :parent acc) text) |
|
4459 |
contents))) |
|
4460 |
;; Result. Set appropriate parent. |
|
4461 |
(if acc (apply #'org-element-set-contents acc (nreverse contents)) |
|
4462 |
(let* ((contents (nreverse contents)) |
|
4463 |
(parent (or parent contents))) |
|
4464 |
(dolist (datum contents contents) |
|
4465 |
(org-element-put-property datum :parent parent)))))))) |
|
4466 |
|
|
4467 |
|
|
4468 |
|
|
4469 |
;;; Towards A Bijective Process |
|
4470 |
;; |
|
4471 |
;; The parse tree obtained with `org-element-parse-buffer' is really |
|
4472 |
;; a snapshot of the corresponding Org buffer. Therefore, it can be |
|
4473 |
;; interpreted and expanded into a string with canonical Org syntax. |
|
4474 |
;; Hence `org-element-interpret-data'. |
|
4475 |
;; |
|
4476 |
;; The function relies internally on |
|
4477 |
;; `org-element--interpret-affiliated-keywords'. |
|
4478 |
|
|
4479 |
;;;###autoload |
|
4480 |
(defun org-element-interpret-data (data) |
|
4481 |
"Interpret DATA as Org syntax. |
|
4482 |
DATA is a parse tree, an element, an object or a secondary string |
|
4483 |
to interpret. Return Org syntax as a string." |
|
4484 |
(letrec ((fun |
|
4485 |
(lambda (data parent) |
|
4486 |
(let* ((type (org-element-type data)) |
|
4487 |
;; Find interpreter for current object or |
|
4488 |
;; element. If it doesn't exist (e.g. this is |
|
4489 |
;; a pseudo object or element), return contents, |
|
4490 |
;; if any. |
|
4491 |
(interpret |
|
4492 |
(let ((fun (intern |
|
4493 |
(format "org-element-%s-interpreter" type)))) |
|
4494 |
(if (fboundp fun) fun (lambda (_ contents) contents)))) |
|
4495 |
(results |
|
4496 |
(cond |
|
4497 |
;; Secondary string. |
|
4498 |
((not type) |
|
4499 |
(mapconcat (lambda (obj) (funcall fun obj parent)) |
|
4500 |
data |
|
4501 |
"")) |
|
4502 |
;; Full Org document. |
|
4503 |
((eq type 'org-data) |
|
4504 |
(mapconcat (lambda (obj) (funcall fun obj parent)) |
|
4505 |
(org-element-contents data) |
|
4506 |
"")) |
|
4507 |
;; Plain text: return it. |
|
4508 |
((stringp data) data) |
|
4509 |
;; Element or object without contents. |
|
4510 |
((not (org-element-contents data)) |
|
4511 |
(funcall interpret data nil)) |
|
4512 |
;; Element or object with contents. |
|
4513 |
(t |
|
4514 |
(funcall |
|
4515 |
interpret |
|
4516 |
data |
|
4517 |
;; Recursively interpret contents. |
|
4518 |
(mapconcat |
|
4519 |
(lambda (datum) (funcall fun datum data)) |
|
4520 |
(org-element-contents |
|
4521 |
(if (not (memq type '(paragraph verse-block))) |
|
4522 |
data |
|
4523 |
;; Fix indentation of elements containing |
|
4524 |
;; objects. We ignore `table-row' |
|
4525 |
;; elements as they are one line long |
|
4526 |
;; anyway. |
|
4527 |
(org-element-normalize-contents |
|
4528 |
data |
|
4529 |
;; When normalizing first paragraph of |
|
4530 |
;; an item or a footnote-definition, |
|
4531 |
;; ignore first line's indentation. |
|
4532 |
(and (eq type 'paragraph) |
|
4533 |
(memq (org-element-type parent) |
|
4534 |
'(footnote-definition item)) |
|
4535 |
(eq data |
|
4536 |
(car (org-element-contents parent))))))) |
|
4537 |
"")))))) |
|
4538 |
(if (memq type '(org-data plain-text nil)) results |
|
4539 |
;; Build white spaces. If no `:post-blank' property |
|
4540 |
;; is specified, assume its value is 0. |
|
4541 |
(let ((blank (or (org-element-property :post-blank data) 0))) |
|
4542 |
(if (eq (org-element-class data parent) 'object) |
|
4543 |
(concat results (make-string blank ?\s)) |
|
4544 |
(concat (org-element--interpret-affiliated-keywords data) |
|
4545 |
(org-element-normalize-string results) |
|
4546 |
(make-string blank ?\n))))))))) |
|
4547 |
(funcall fun data nil))) |
|
4548 |
|
|
4549 |
(defun org-element--interpret-affiliated-keywords (element) |
|
4550 |
"Return ELEMENT's affiliated keywords as Org syntax. |
|
4551 |
If there is no affiliated keyword, return the empty string." |
|
4552 |
(let ((keyword-to-org |
|
4553 |
(function |
|
4554 |
(lambda (key value) |
|
4555 |
(let (dual) |
|
4556 |
(when (member key org-element-dual-keywords) |
|
4557 |
(setq dual (cdr value) value (car value))) |
|
4558 |
(concat "#+" key |
|
4559 |
(and dual |
|
4560 |
(format "[%s]" (org-element-interpret-data dual))) |
|
4561 |
": " |
|
4562 |
(if (member key org-element-parsed-keywords) |
|
4563 |
(org-element-interpret-data value) |
|
4564 |
value) |
|
4565 |
"\n")))))) |
|
4566 |
(mapconcat |
|
4567 |
(lambda (prop) |
|
4568 |
(let ((value (org-element-property prop element)) |
|
4569 |
(keyword (upcase (substring (symbol-name prop) 1)))) |
|
4570 |
(when value |
|
4571 |
(if (or (member keyword org-element-multiple-keywords) |
|
4572 |
;; All attribute keywords can have multiple lines. |
|
4573 |
(string-match "^ATTR_" keyword)) |
|
4574 |
(mapconcat (lambda (line) (funcall keyword-to-org keyword line)) |
|
4575 |
(reverse value) |
|
4576 |
"") |
|
4577 |
(funcall keyword-to-org keyword value))))) |
|
4578 |
;; List all ELEMENT's properties matching an attribute line or an |
|
4579 |
;; affiliated keyword, but ignore translated keywords since they |
|
4580 |
;; cannot belong to the property list. |
|
4581 |
(cl-loop for prop in (nth 1 element) by 'cddr |
|
4582 |
when (let ((keyword (upcase (substring (symbol-name prop) 1)))) |
|
4583 |
(or (string-match "^ATTR_" keyword) |
|
4584 |
(and |
|
4585 |
(member keyword org-element-affiliated-keywords) |
|
4586 |
(not (assoc keyword |
|
4587 |
org-element-keyword-translation-alist))))) |
|
4588 |
collect prop) |
|
4589 |
""))) |
|
4590 |
|
|
4591 |
;; Because interpretation of the parse tree must return the same |
|
4592 |
;; number of blank lines between elements and the same number of white |
|
4593 |
;; space after objects, some special care must be given to white |
|
4594 |
;; spaces. |
|
4595 |
;; |
|
4596 |
;; The first function, `org-element-normalize-string', ensures any |
|
4597 |
;; string different from the empty string will end with a single |
|
4598 |
;; newline character. |
|
4599 |
;; |
|
4600 |
;; The second function, `org-element-normalize-contents', removes |
|
4601 |
;; global indentation from the contents of the current element. |
|
4602 |
|
|
4603 |
(defun org-element-normalize-string (s) |
|
4604 |
"Ensure string S ends with a single newline character. |
|
4605 |
|
|
4606 |
If S isn't a string return it unchanged. If S is the empty |
|
4607 |
string, return it. Otherwise, return a new string with a single |
|
4608 |
newline character at its end." |
|
4609 |
(cond |
|
4610 |
((not (stringp s)) s) |
|
4611 |
((string= "" s) "") |
|
4612 |
(t (and (string-match "\\(\n[ \t]*\\)*\\'" s) |
|
4613 |
(replace-match "\n" nil nil s))))) |
|
4614 |
|
|
4615 |
(defun org-element-normalize-contents (element &optional ignore-first) |
|
4616 |
"Normalize plain text in ELEMENT's contents. |
|
4617 |
|
|
4618 |
ELEMENT must only contain plain text and objects. |
|
4619 |
|
|
4620 |
If optional argument IGNORE-FIRST is non-nil, ignore first line's |
|
4621 |
indentation to compute maximal common indentation. |
|
4622 |
|
|
4623 |
Return the normalized element that is element with global |
|
4624 |
indentation removed from its contents." |
|
4625 |
(letrec ((find-min-ind |
|
4626 |
;; Return minimal common indentation within BLOB. This is |
|
4627 |
;; done by walking recursively BLOB and updating MIN-IND |
|
4628 |
;; along the way. FIRST-FLAG is non-nil when the next |
|
4629 |
;; object is expected to be a string that doesn't start |
|
4630 |
;; with a newline character. It happens for strings at |
|
4631 |
;; the beginnings of the contents or right after a line |
|
4632 |
;; break. |
|
4633 |
(lambda (blob first-flag min-ind) |
|
4634 |
(dolist (datum (org-element-contents blob) min-ind) |
|
4635 |
(when first-flag |
|
4636 |
(setq first-flag nil) |
|
4637 |
(cond |
|
4638 |
;; Objects cannot start with spaces: in this |
|
4639 |
;; case, indentation is 0. |
|
4640 |
((not (stringp datum)) (throw :zero 0)) |
|
4641 |
((not (string-match |
|
4642 |
"\\`\\([ \t]+\\)\\([^ \t\n]\\|\n\\|\\'\\)" datum)) |
|
4643 |
(throw :zero 0)) |
|
4644 |
((equal (match-string 2 datum) "\n") |
|
4645 |
(put-text-property |
|
4646 |
(match-beginning 1) (match-end 1) 'org-ind 'empty datum)) |
|
4647 |
(t |
|
4648 |
(let ((i (string-width (match-string 1 datum)))) |
|
4649 |
(put-text-property |
|
4650 |
(match-beginning 1) (match-end 1) 'org-ind i datum) |
|
4651 |
(setq min-ind (min i min-ind)))))) |
|
4652 |
(cond |
|
4653 |
((stringp datum) |
|
4654 |
(let ((s 0)) |
|
4655 |
(while (string-match |
|
4656 |
"\n\\([ \t]*\\)\\([^ \t\n]\\|\n\\|\\'\\)" datum s) |
|
4657 |
(setq s (match-end 1)) |
|
4658 |
(cond |
|
4659 |
((equal (match-string 1 datum) "") |
|
4660 |
(unless (member (match-string 2 datum) '("" "\n")) |
|
4661 |
(throw :zero 0))) |
|
4662 |
((equal (match-string 2 datum) "\n") |
|
4663 |
(put-text-property (match-beginning 1) (match-end 1) |
|
4664 |
'org-ind 'empty datum)) |
|
4665 |
(t |
|
4666 |
(let ((i (string-width (match-string 1 datum)))) |
|
4667 |
(put-text-property (match-beginning 1) (match-end 1) |
|
4668 |
'org-ind i datum) |
|
4669 |
(setq min-ind (min i min-ind)))))))) |
|
4670 |
((eq (org-element-type datum) 'line-break) |
|
4671 |
(setq first-flag t)) |
|
4672 |
((memq (org-element-type datum) org-element-recursive-objects) |
|
4673 |
(setq min-ind |
|
4674 |
(funcall find-min-ind datum first-flag min-ind))))))) |
|
4675 |
(min-ind |
|
4676 |
(catch :zero |
|
4677 |
(funcall find-min-ind |
|
4678 |
element (not ignore-first) most-positive-fixnum)))) |
|
4679 |
(if (or (zerop min-ind) (= min-ind most-positive-fixnum)) element |
|
4680 |
;; Build ELEMENT back, replacing each string with the same |
|
4681 |
;; string minus common indentation. |
|
4682 |
(letrec ((build |
|
4683 |
(lambda (datum) |
|
4684 |
;; Return DATUM with all its strings indentation |
|
4685 |
;; shortened from MIN-IND white spaces. |
|
4686 |
(setcdr |
|
4687 |
(cdr datum) |
|
4688 |
(mapcar |
|
4689 |
(lambda (object) |
|
4690 |
(cond |
|
4691 |
((stringp object) |
|
4692 |
(with-temp-buffer |
|
4693 |
(insert object) |
|
4694 |
(let ((s (point-min))) |
|
4695 |
(while (setq s (text-property-not-all |
|
4696 |
s (point-max) 'org-ind nil)) |
|
4697 |
(goto-char s) |
|
4698 |
(let ((i (get-text-property s 'org-ind))) |
|
4699 |
(delete-region s (progn |
|
4700 |
(skip-chars-forward " \t") |
|
4701 |
(point))) |
|
4702 |
(when (integerp i) (indent-to (- i min-ind)))))) |
|
4703 |
(buffer-string))) |
|
4704 |
((memq (org-element-type object) |
|
4705 |
org-element-recursive-objects) |
|
4706 |
(funcall build object)) |
|
4707 |
(t object))) |
|
4708 |
(org-element-contents datum))) |
|
4709 |
datum))) |
|
4710 |
(funcall build element))))) |
|
4711 |
|
|
4712 |
|
|
4713 |
|
|
4714 |
;;; Cache |
|
4715 |
;; |
|
4716 |
;; Implement a caching mechanism for `org-element-at-point' and |
|
4717 |
;; `org-element-context', which see. |
|
4718 |
;; |
|
4719 |
;; A single public function is provided: `org-element-cache-reset'. |
|
4720 |
;; |
|
4721 |
;; Cache is enabled by default, but can be disabled globally with |
|
4722 |
;; `org-element-use-cache'. `org-element-cache-sync-idle-time', |
|
4723 |
;; org-element-cache-sync-duration' and `org-element-cache-sync-break' |
|
4724 |
;; can be tweaked to control caching behavior. |
|
4725 |
;; |
|
4726 |
;; Internally, parsed elements are stored in an AVL tree, |
|
4727 |
;; `org-element--cache'. This tree is updated lazily: whenever |
|
4728 |
;; a change happens to the buffer, a synchronization request is |
|
4729 |
;; registered in `org-element--cache-sync-requests' (see |
|
4730 |
;; `org-element--cache-submit-request'). During idle time, requests |
|
4731 |
;; are processed by `org-element--cache-sync'. Synchronization also |
|
4732 |
;; happens when an element is required from the cache. In this case, |
|
4733 |
;; the process stops as soon as the needed element is up-to-date. |
|
4734 |
;; |
|
4735 |
;; A synchronization request can only apply on a synchronized part of |
|
4736 |
;; the cache. Therefore, the cache is updated at least to the |
|
4737 |
;; location where the new request applies. Thus, requests are ordered |
|
4738 |
;; from left to right and all elements starting before the first |
|
4739 |
;; request are correct. This property is used by functions like |
|
4740 |
;; `org-element--cache-find' to retrieve elements in the part of the |
|
4741 |
;; cache that can be trusted. |
|
4742 |
;; |
|
4743 |
;; A request applies to every element, starting from its original |
|
4744 |
;; location (or key, see below). When a request is processed, it |
|
4745 |
;; moves forward and may collide the next one. In this case, both |
|
4746 |
;; requests are merged into a new one that starts from that element. |
|
4747 |
;; As a consequence, the whole synchronization complexity does not |
|
4748 |
;; depend on the number of pending requests, but on the number of |
|
4749 |
;; elements the very first request will be applied on. |
|
4750 |
;; |
|
4751 |
;; Elements cannot be accessed through their beginning position, which |
|
4752 |
;; may or may not be up-to-date. Instead, each element in the tree is |
|
4753 |
;; associated to a key, obtained with `org-element--cache-key'. This |
|
4754 |
;; mechanism is robust enough to preserve total order among elements |
|
4755 |
;; even when the tree is only partially synchronized. |
|
4756 |
|
|
4757 |
|
|
4758 |
(defvar org-element-use-cache nil |
|
4759 |
"Non-nil when Org parser should cache its results. |
|
4760 |
|
|
4761 |
WARNING: for the time being, using cache sometimes triggers |
|
4762 |
freezes. Therefore, it is disabled by default. Activate it if |
|
4763 |
you want to help debugging the issue.") |
|
4764 |
|
|
4765 |
(defvar org-element-cache-sync-idle-time 0.6 |
|
4766 |
"Length, in seconds, of idle time before syncing cache.") |
|
4767 |
|
|
4768 |
(defvar org-element-cache-sync-duration (seconds-to-time 0.04) |
|
4769 |
"Maximum duration, as a time value, for a cache synchronization. |
|
4770 |
If the synchronization is not over after this delay, the process |
|
4771 |
pauses and resumes after `org-element-cache-sync-break' |
|
4772 |
seconds.") |
|
4773 |
|
|
4774 |
(defvar org-element-cache-sync-break (seconds-to-time 0.3) |
|
4775 |
"Duration, as a time value, of the pause between synchronizations. |
|
4776 |
See `org-element-cache-sync-duration' for more information.") |
|
4777 |
|
|
4778 |
|
|
4779 |
;;;; Data Structure |
|
4780 |
|
|
4781 |
(defvar org-element--cache nil |
|
4782 |
"AVL tree used to cache elements. |
|
4783 |
Each node of the tree contains an element. Comparison is done |
|
4784 |
with `org-element--cache-compare'. This cache is used in |
|
4785 |
`org-element-at-point'.") |
|
4786 |
|
|
4787 |
(defvar org-element--cache-sync-requests nil |
|
4788 |
"List of pending synchronization requests. |
|
4789 |
|
|
4790 |
A request is a vector with the following pattern: |
|
4791 |
|
|
4792 |
\[NEXT BEG END OFFSET PARENT PHASE] |
|
4793 |
|
|
4794 |
Processing a synchronization request consists of three phases: |
|
4795 |
|
|
4796 |
0. Delete modified elements, |
|
4797 |
1. Fill missing area in cache, |
|
4798 |
2. Shift positions and re-parent elements after the changes. |
|
4799 |
|
|
4800 |
During phase 0, NEXT is the key of the first element to be |
|
4801 |
removed, BEG and END is buffer position delimiting the |
|
4802 |
modifications. Elements starting between them (inclusive) are |
|
4803 |
removed. So are elements whose parent is removed. PARENT, when |
|
4804 |
non-nil, is the parent of the first element to be removed. |
|
4805 |
|
|
4806 |
During phase 1, NEXT is the key of the next known element in |
|
4807 |
cache and BEG its beginning position. Parse buffer between that |
|
4808 |
element and the one before it in order to determine the parent of |
|
4809 |
the next element. Set PARENT to the element containing NEXT. |
|
4810 |
|
|
4811 |
During phase 2, NEXT is the key of the next element to shift in |
|
4812 |
the parse tree. All elements starting from this one have their |
|
4813 |
properties relatives to buffer positions shifted by integer |
|
4814 |
OFFSET and, if they belong to element PARENT, are adopted by it. |
|
4815 |
|
|
4816 |
PHASE specifies the phase number, as an integer.") |
|
4817 |
|
|
4818 |
(defvar org-element--cache-sync-timer nil |
|
4819 |
"Timer used for cache synchronization.") |
|
4820 |
|
|
4821 |
(defvar org-element--cache-sync-keys nil |
|
4822 |
"Hash table used to store keys during synchronization. |
|
4823 |
See `org-element--cache-key' for more information.") |
|
4824 |
|
|
4825 |
(defsubst org-element--cache-key (element) |
|
4826 |
"Return a unique key for ELEMENT in cache tree. |
|
4827 |
|
|
4828 |
Keys are used to keep a total order among elements in the cache. |
|
4829 |
Comparison is done with `org-element--cache-key-less-p'. |
|
4830 |
|
|
4831 |
When no synchronization is taking place, a key is simply the |
|
4832 |
beginning position of the element, or that position plus one in |
|
4833 |
the case of an first item (respectively row) in |
|
4834 |
a list (respectively a table). |
|
4835 |
|
|
4836 |
During a synchronization, the key is the one the element had when |
|
4837 |
the cache was synchronized for the last time. Elements added to |
|
4838 |
cache during the synchronization get a new key generated with |
|
4839 |
`org-element--cache-generate-key'. |
|
4840 |
|
|
4841 |
Such keys are stored in `org-element--cache-sync-keys'. The hash |
|
4842 |
table is cleared once the synchronization is complete." |
|
4843 |
(or (gethash element org-element--cache-sync-keys) |
|
4844 |
(let* ((begin (org-element-property :begin element)) |
|
4845 |
;; Increase beginning position of items (respectively |
|
4846 |
;; table rows) by one, so the first item can get |
|
4847 |
;; a different key from its parent list (respectively |
|
4848 |
;; table). |
|
4849 |
(key (if (memq (org-element-type element) '(item table-row)) |
|
4850 |
(1+ begin) |
|
4851 |
begin))) |
|
4852 |
(if org-element--cache-sync-requests |
|
4853 |
(puthash element key org-element--cache-sync-keys) |
|
4854 |
key)))) |
|
4855 |
|
|
4856 |
(defun org-element--cache-generate-key (lower upper) |
|
4857 |
"Generate a key between LOWER and UPPER. |
|
4858 |
|
|
4859 |
LOWER and UPPER are integers or lists, possibly empty. |
|
4860 |
|
|
4861 |
If LOWER and UPPER are equals, return LOWER. Otherwise, return |
|
4862 |
a unique key, as an integer or a list of integers, according to |
|
4863 |
the following rules: |
|
4864 |
|
|
4865 |
- LOWER and UPPER are compared level-wise until values differ. |
|
4866 |
|
|
4867 |
- If, at a given level, LOWER and UPPER differ from more than |
|
4868 |
2, the new key shares all the levels above with LOWER and |
|
4869 |
gets a new level. Its value is the mean between LOWER and |
|
4870 |
UPPER: |
|
4871 |
|
|
4872 |
(1 2) + (1 4) --> (1 3) |
|
4873 |
|
|
4874 |
- If LOWER has no value to compare with, it is assumed that its |
|
4875 |
value is `most-negative-fixnum'. E.g., |
|
4876 |
|
|
4877 |
(1 1) + (1 1 2) |
|
4878 |
|
|
4879 |
is equivalent to |
|
4880 |
|
|
4881 |
(1 1 m) + (1 1 2) |
|
4882 |
|
|
4883 |
where m is `most-negative-fixnum'. Likewise, if UPPER is |
|
4884 |
short of levels, the current value is `most-positive-fixnum'. |
|
4885 |
|
|
4886 |
- If they differ from only one, the new key inherits from |
|
4887 |
current LOWER level and fork it at the next level. E.g., |
|
4888 |
|
|
4889 |
(2 1) + (3 3) |
|
4890 |
|
|
4891 |
is equivalent to |
|
4892 |
|
|
4893 |
(2 1) + (2 M) |
|
4894 |
|
|
4895 |
where M is `most-positive-fixnum'. |
|
4896 |
|
|
4897 |
- If the key is only one level long, it is returned as an |
|
4898 |
integer: |
|
4899 |
|
|
4900 |
(1 2) + (3 2) --> 2 |
|
4901 |
|
|
4902 |
When they are not equals, the function assumes that LOWER is |
|
4903 |
lesser than UPPER, per `org-element--cache-key-less-p'." |
|
4904 |
(if (equal lower upper) lower |
|
4905 |
(let ((lower (if (integerp lower) (list lower) lower)) |
|
4906 |
(upper (if (integerp upper) (list upper) upper)) |
|
4907 |
skip-upper key) |
|
4908 |
(catch 'exit |
|
4909 |
(while t |
|
4910 |
(let ((min (or (car lower) most-negative-fixnum)) |
|
4911 |
(max (cond (skip-upper most-positive-fixnum) |
|
4912 |
((car upper)) |
|
4913 |
(t most-positive-fixnum)))) |
|
4914 |
(if (< (1+ min) max) |
|
4915 |
(let ((mean (+ (ash min -1) (ash max -1) (logand min max 1)))) |
|
4916 |
(throw 'exit (if key (nreverse (cons mean key)) mean))) |
|
4917 |
(when (and (< min max) (not skip-upper)) |
|
4918 |
;; When at a given level, LOWER and UPPER differ from |
|
4919 |
;; 1, ignore UPPER altogether. Instead create a key |
|
4920 |
;; between LOWER and the greatest key with the same |
|
4921 |
;; prefix as LOWER so far. |
|
4922 |
(setq skip-upper t)) |
|
4923 |
(push min key) |
|
4924 |
(setq lower (cdr lower) upper (cdr upper))))))))) |
|
4925 |
|
|
4926 |
(defsubst org-element--cache-key-less-p (a b) |
|
4927 |
"Non-nil if key A is less than key B. |
|
4928 |
A and B are either integers or lists of integers, as returned by |
|
4929 |
`org-element--cache-key'." |
|
4930 |
(if (integerp a) (if (integerp b) (< a b) (<= a (car b))) |
|
4931 |
(if (integerp b) (< (car a) b) |
|
4932 |
(catch 'exit |
|
4933 |
(while (and a b) |
|
4934 |
(cond ((car-less-than-car a b) (throw 'exit t)) |
|
4935 |
((car-less-than-car b a) (throw 'exit nil)) |
|
4936 |
(t (setq a (cdr a) b (cdr b))))) |
|
4937 |
;; If A is empty, either keys are equal (B is also empty) and |
|
4938 |
;; we return nil, or A is lesser than B (B is longer) and we |
|
4939 |
;; return a non-nil value. |
|
4940 |
;; |
|
4941 |
;; If A is not empty, B is necessarily empty and A is greater |
|
4942 |
;; than B (A is longer). Therefore, return nil. |
|
4943 |
(and (null a) b))))) |
|
4944 |
|
|
4945 |
(defun org-element--cache-compare (a b) |
|
4946 |
"Non-nil when element A is located before element B." |
|
4947 |
(org-element--cache-key-less-p (org-element--cache-key a) |
|
4948 |
(org-element--cache-key b))) |
|
4949 |
|
|
4950 |
(defsubst org-element--cache-root () |
|
4951 |
"Return root value in cache. |
|
4952 |
This function assumes `org-element--cache' is a valid AVL tree." |
|
4953 |
(avl-tree--node-left (avl-tree--dummyroot org-element--cache))) |
|
4954 |
|
|
4955 |
|
|
4956 |
;;;; Tools |
|
4957 |
|
|
4958 |
(defsubst org-element--cache-active-p () |
|
4959 |
"Non-nil when cache is active in current buffer." |
|
4960 |
(and org-element-use-cache |
|
4961 |
org-element--cache |
|
4962 |
(derived-mode-p 'org-mode))) |
|
4963 |
|
|
4964 |
(defun org-element--cache-find (pos &optional side) |
|
4965 |
"Find element in cache starting at POS or before. |
|
4966 |
|
|
4967 |
POS refers to a buffer position. |
|
4968 |
|
|
4969 |
When optional argument SIDE is non-nil, the function checks for |
|
4970 |
elements starting at or past POS instead. If SIDE is `both', the |
|
4971 |
function returns a cons cell where car is the first element |
|
4972 |
starting at or before POS and cdr the first element starting |
|
4973 |
after POS. |
|
4974 |
|
|
4975 |
The function can only find elements in the synchronized part of |
|
4976 |
the cache." |
|
4977 |
(let ((limit (and org-element--cache-sync-requests |
|
4978 |
(aref (car org-element--cache-sync-requests) 0))) |
|
4979 |
(node (org-element--cache-root)) |
|
4980 |
lower upper) |
|
4981 |
(while node |
|
4982 |
(let* ((element (avl-tree--node-data node)) |
|
4983 |
(begin (org-element-property :begin element))) |
|
4984 |
(cond |
|
4985 |
((and limit |
|
4986 |
(not (org-element--cache-key-less-p |
|
4987 |
(org-element--cache-key element) limit))) |
|
4988 |
(setq node (avl-tree--node-left node))) |
|
4989 |
((> begin pos) |
|
4990 |
(setq upper element |
|
4991 |
node (avl-tree--node-left node))) |
|
4992 |
((< begin pos) |
|
4993 |
(setq lower element |
|
4994 |
node (avl-tree--node-right node))) |
|
4995 |
;; We found an element in cache starting at POS. If `side' |
|
4996 |
;; is `both' we also want the next one in order to generate |
|
4997 |
;; a key in-between. |
|
4998 |
;; |
|
4999 |
;; If the element is the first row or item in a table or |
|
5000 |
;; a plain list, we always return the table or the plain |
|
5001 |
;; list. |
|
5002 |
;; |
|
5003 |
;; In any other case, we return the element found. |
|
5004 |
((eq side 'both) |
|
5005 |
(setq lower element) |
|
5006 |
(setq node (avl-tree--node-right node))) |
|
5007 |
((and (memq (org-element-type element) '(item table-row)) |
|
5008 |
(let ((parent (org-element-property :parent element))) |
|
5009 |
(and (= (org-element-property :begin element) |
|
5010 |
(org-element-property :contents-begin parent)) |
|
5011 |
(setq node nil |
|
5012 |
lower parent |
|
5013 |
upper parent))))) |
|
5014 |
(t |
|
5015 |
(setq node nil |
|
5016 |
lower element |
|
5017 |
upper element))))) |
|
5018 |
(pcase side |
|
5019 |
(`both (cons lower upper)) |
|
5020 |
(`nil lower) |
|
5021 |
(_ upper)))) |
|
5022 |
|
|
5023 |
(defun org-element--cache-put (element) |
|
5024 |
"Store ELEMENT in current buffer's cache, if allowed." |
|
5025 |
(when (org-element--cache-active-p) |
|
5026 |
(when org-element--cache-sync-requests |
|
5027 |
;; During synchronization, first build an appropriate key for |
|
5028 |
;; the new element so `avl-tree-enter' can insert it at the |
|
5029 |
;; right spot in the cache. |
|
5030 |
(let ((keys (org-element--cache-find |
|
5031 |
(org-element-property :begin element) 'both))) |
|
5032 |
(puthash element |
|
5033 |
(org-element--cache-generate-key |
|
5034 |
(and (car keys) (org-element--cache-key (car keys))) |
|
5035 |
(cond ((cdr keys) (org-element--cache-key (cdr keys))) |
|
5036 |
(org-element--cache-sync-requests |
|
5037 |
(aref (car org-element--cache-sync-requests) 0)))) |
|
5038 |
org-element--cache-sync-keys))) |
|
5039 |
(avl-tree-enter org-element--cache element))) |
|
5040 |
|
|
5041 |
(defsubst org-element--cache-remove (element) |
|
5042 |
"Remove ELEMENT from cache. |
|
5043 |
Assume ELEMENT belongs to cache and that a cache is active." |
|
5044 |
(avl-tree-delete org-element--cache element)) |
|
5045 |
|
|
5046 |
|
|
5047 |
;;;; Synchronization |
|
5048 |
|
|
5049 |
(defsubst org-element--cache-set-timer (buffer) |
|
5050 |
"Set idle timer for cache synchronization in BUFFER." |
|
5051 |
(when org-element--cache-sync-timer |
|
5052 |
(cancel-timer org-element--cache-sync-timer)) |
|
5053 |
(setq org-element--cache-sync-timer |
|
5054 |
(run-with-idle-timer |
|
5055 |
(let ((idle (current-idle-time))) |
|
5056 |
(if idle (time-add idle org-element-cache-sync-break) |
|
5057 |
org-element-cache-sync-idle-time)) |
|
5058 |
nil |
|
5059 |
#'org-element--cache-sync |
|
5060 |
buffer))) |
|
5061 |
|
|
5062 |
(defsubst org-element--cache-interrupt-p (time-limit) |
|
5063 |
"Non-nil when synchronization process should be interrupted. |
|
5064 |
TIME-LIMIT is a time value or nil." |
|
5065 |
(and time-limit |
|
5066 |
(or (input-pending-p) |
|
5067 |
(time-less-p time-limit (current-time))))) |
|
5068 |
|
|
5069 |
(defsubst org-element--cache-shift-positions (element offset &optional props) |
|
5070 |
"Shift ELEMENT properties relative to buffer positions by OFFSET. |
|
5071 |
|
|
5072 |
Properties containing buffer positions are `:begin', `:end', |
|
5073 |
`:contents-begin', `:contents-end' and `:structure'. When |
|
5074 |
optional argument PROPS is a list of keywords, only shift |
|
5075 |
properties provided in that list. |
|
5076 |
|
|
5077 |
Properties are modified by side-effect." |
|
5078 |
(let ((properties (nth 1 element))) |
|
5079 |
;; Shift `:structure' property for the first plain list only: it |
|
5080 |
;; is the only one that really matters and it prevents from |
|
5081 |
;; shifting it more than once. |
|
5082 |
(when (and (or (not props) (memq :structure props)) |
|
5083 |
(eq (org-element-type element) 'plain-list) |
|
5084 |
(not (eq (org-element-type (plist-get properties :parent)) |
|
5085 |
'item))) |
|
5086 |
(dolist (item (plist-get properties :structure)) |
|
5087 |
(cl-incf (car item) offset) |
|
5088 |
(cl-incf (nth 6 item) offset))) |
|
5089 |
(dolist (key '(:begin :contents-begin :contents-end :end :post-affiliated)) |
|
5090 |
(let ((value (and (or (not props) (memq key props)) |
|
5091 |
(plist-get properties key)))) |
|
5092 |
(and value (plist-put properties key (+ offset value))))))) |
|
5093 |
|
|
5094 |
(defun org-element--cache-sync (buffer &optional threshold future-change) |
|
5095 |
"Synchronize cache with recent modification in BUFFER. |
|
5096 |
|
|
5097 |
When optional argument THRESHOLD is non-nil, do the |
|
5098 |
synchronization for all elements starting before or at threshold, |
|
5099 |
then exit. Otherwise, synchronize cache for as long as |
|
5100 |
`org-element-cache-sync-duration' or until Emacs leaves idle |
|
5101 |
state. |
|
5102 |
|
|
5103 |
FUTURE-CHANGE, when non-nil, is a buffer position where changes |
|
5104 |
not registered yet in the cache are going to happen. It is used |
|
5105 |
in `org-element--cache-submit-request', where cache is partially |
|
5106 |
updated before current modification are actually submitted." |
|
5107 |
(when (buffer-live-p buffer) |
|
5108 |
(with-current-buffer buffer |
|
5109 |
(let ((inhibit-quit t) request next) |
|
5110 |
(when org-element--cache-sync-timer |
|
5111 |
(cancel-timer org-element--cache-sync-timer)) |
|
5112 |
(catch 'interrupt |
|
5113 |
(while org-element--cache-sync-requests |
|
5114 |
(setq request (car org-element--cache-sync-requests) |
|
5115 |
next (nth 1 org-element--cache-sync-requests)) |
|
5116 |
(org-element--cache-process-request |
|
5117 |
request |
|
5118 |
(and next (aref next 0)) |
|
5119 |
threshold |
|
5120 |
(and (not threshold) |
|
5121 |
(time-add (current-time) |
|
5122 |
org-element-cache-sync-duration)) |
|
5123 |
future-change) |
|
5124 |
;; Request processed. Merge current and next offsets and |
|
5125 |
;; transfer ending position. |
|
5126 |
(when next |
|
5127 |
(cl-incf (aref next 3) (aref request 3)) |
|
5128 |
(aset next 2 (aref request 2))) |
|
5129 |
(setq org-element--cache-sync-requests |
|
5130 |
(cdr org-element--cache-sync-requests)))) |
|
5131 |
;; If more requests are awaiting, set idle timer accordingly. |
|
5132 |
;; Otherwise, reset keys. |
|
5133 |
(if org-element--cache-sync-requests |
|
5134 |
(org-element--cache-set-timer buffer) |
|
5135 |
(clrhash org-element--cache-sync-keys)))))) |
|
5136 |
|
|
5137 |
(defun org-element--cache-process-request |
|
5138 |
(request next threshold time-limit future-change) |
|
5139 |
"Process synchronization REQUEST for all entries before NEXT. |
|
5140 |
|
|
5141 |
REQUEST is a vector, built by `org-element--cache-submit-request'. |
|
5142 |
|
|
5143 |
NEXT is a cache key, as returned by `org-element--cache-key'. |
|
5144 |
|
|
5145 |
When non-nil, THRESHOLD is a buffer position. Synchronization |
|
5146 |
stops as soon as a shifted element begins after it. |
|
5147 |
|
|
5148 |
When non-nil, TIME-LIMIT is a time value. Synchronization stops |
|
5149 |
after this time or when Emacs exits idle state. |
|
5150 |
|
|
5151 |
When non-nil, FUTURE-CHANGE is a buffer position where changes |
|
5152 |
not registered yet in the cache are going to happen. See |
|
5153 |
`org-element--cache-submit-request' for more information. |
|
5154 |
|
|
5155 |
Throw `interrupt' if the process stops before completing the |
|
5156 |
request." |
|
5157 |
(catch 'quit |
|
5158 |
(when (= (aref request 5) 0) |
|
5159 |
;; Phase 0. |
|
5160 |
;; |
|
5161 |
;; Delete all elements starting after BEG, but not after buffer |
|
5162 |
;; position END or past element with key NEXT. Also delete |
|
5163 |
;; elements contained within a previously removed element |
|
5164 |
;; (stored in `last-container'). |
|
5165 |
;; |
|
5166 |
;; At each iteration, we start again at tree root since |
|
5167 |
;; a deletion modifies structure of the balanced tree. |
|
5168 |
(catch 'end-phase |
|
5169 |
(while t |
|
5170 |
(when (org-element--cache-interrupt-p time-limit) |
|
5171 |
(throw 'interrupt nil)) |
|
5172 |
;; Find first element in cache with key BEG or after it. |
|
5173 |
(let ((beg (aref request 0)) |
|
5174 |
(end (aref request 2)) |
|
5175 |
(node (org-element--cache-root)) |
|
5176 |
data data-key last-container) |
|
5177 |
(while node |
|
5178 |
(let* ((element (avl-tree--node-data node)) |
|
5179 |
(key (org-element--cache-key element))) |
|
5180 |
(cond |
|
5181 |
((org-element--cache-key-less-p key beg) |
|
5182 |
(setq node (avl-tree--node-right node))) |
|
5183 |
((org-element--cache-key-less-p beg key) |
|
5184 |
(setq data element |
|
5185 |
data-key key |
|
5186 |
node (avl-tree--node-left node))) |
|
5187 |
(t (setq data element |
|
5188 |
data-key key |
|
5189 |
node nil))))) |
|
5190 |
(if data |
|
5191 |
(let ((pos (org-element-property :begin data))) |
|
5192 |
(if (if (or (not next) |
|
5193 |
(org-element--cache-key-less-p data-key next)) |
|
5194 |
(<= pos end) |
|
5195 |
(and last-container |
|
5196 |
(let ((up data)) |
|
5197 |
(while (and up (not (eq up last-container))) |
|
5198 |
(setq up (org-element-property :parent up))) |
|
5199 |
up))) |
|
5200 |
(progn (when (and (not last-container) |
|
5201 |
(> (org-element-property :end data) |
|
5202 |
end)) |
|
5203 |
(setq last-container data)) |
|
5204 |
(org-element--cache-remove data)) |
|
5205 |
(aset request 0 data-key) |
|
5206 |
(aset request 1 pos) |
|
5207 |
(aset request 5 1) |
|
5208 |
(throw 'end-phase nil))) |
|
5209 |
;; No element starting after modifications left in |
|
5210 |
;; cache: further processing is futile. |
|
5211 |
(throw 'quit t)))))) |
|
5212 |
(when (= (aref request 5) 1) |
|
5213 |
;; Phase 1. |
|
5214 |
;; |
|
5215 |
;; Phase 0 left a hole in the cache. Some elements after it |
|
5216 |
;; could have parents within. For example, in the following |
|
5217 |
;; buffer: |
|
5218 |
;; |
|
5219 |
;; - item |
|
5220 |
;; |
|
5221 |
;; |
|
5222 |
;; Paragraph1 |
|
5223 |
;; |
|
5224 |
;; Paragraph2 |
|
5225 |
;; |
|
5226 |
;; if we remove a blank line between "item" and "Paragraph1", |
|
5227 |
;; everything down to "Paragraph2" is removed from cache. But |
|
5228 |
;; the paragraph now belongs to the list, and its `:parent' |
|
5229 |
;; property no longer is accurate. |
|
5230 |
;; |
|
5231 |
;; Therefore we need to parse again elements in the hole, or at |
|
5232 |
;; least in its last section, so that we can re-parent |
|
5233 |
;; subsequent elements, during phase 2. |
|
5234 |
;; |
|
5235 |
;; Note that we only need to get the parent from the first |
|
5236 |
;; element in cache after the hole. |
|
5237 |
;; |
|
5238 |
;; When next key is lesser or equal to the current one, delegate |
|
5239 |
;; phase 1 processing to next request in order to preserve key |
|
5240 |
;; order among requests. |
|
5241 |
(let ((key (aref request 0))) |
|
5242 |
(when (and next (not (org-element--cache-key-less-p key next))) |
|
5243 |
(let ((next-request (nth 1 org-element--cache-sync-requests))) |
|
5244 |
(aset next-request 0 key) |
|
5245 |
(aset next-request 1 (aref request 1)) |
|
5246 |
(aset next-request 5 1)) |
|
5247 |
(throw 'quit t))) |
|
5248 |
;; Next element will start at its beginning position plus |
|
5249 |
;; offset, since it hasn't been shifted yet. Therefore, LIMIT |
|
5250 |
;; contains the real beginning position of the first element to |
|
5251 |
;; shift and re-parent. |
|
5252 |
(let ((limit (+ (aref request 1) (aref request 3)))) |
|
5253 |
(cond ((and threshold (> limit threshold)) (throw 'interrupt nil)) |
|
5254 |
((and future-change (>= limit future-change)) |
|
5255 |
;; Changes are going to happen around this element and |
|
5256 |
;; they will trigger another phase 1 request. Skip the |
|
5257 |
;; current one. |
|
5258 |
(aset request 5 2)) |
|
5259 |
(t |
|
5260 |
(let ((parent (org-element--parse-to limit t time-limit))) |
|
5261 |
(aset request 4 parent) |
|
5262 |
(aset request 5 2)))))) |
|
5263 |
;; Phase 2. |
|
5264 |
;; |
|
5265 |
;; Shift all elements starting from key START, but before NEXT, by |
|
5266 |
;; OFFSET, and re-parent them when appropriate. |
|
5267 |
;; |
|
5268 |
;; Elements are modified by side-effect so the tree structure |
|
5269 |
;; remains intact. |
|
5270 |
;; |
|
5271 |
;; Once THRESHOLD, if any, is reached, or once there is an input |
|
5272 |
;; pending, exit. Before leaving, the current synchronization |
|
5273 |
;; request is updated. |
|
5274 |
(let ((start (aref request 0)) |
|
5275 |
(offset (aref request 3)) |
|
5276 |
(parent (aref request 4)) |
|
5277 |
(node (org-element--cache-root)) |
|
5278 |
(stack (list nil)) |
|
5279 |
(leftp t) |
|
5280 |
exit-flag) |
|
5281 |
;; No re-parenting nor shifting planned: request is over. |
|
5282 |
(when (and (not parent) (zerop offset)) (throw 'quit t)) |
|
5283 |
(while node |
|
5284 |
(let* ((data (avl-tree--node-data node)) |
|
5285 |
(key (org-element--cache-key data))) |
|
5286 |
(if (and leftp (avl-tree--node-left node) |
|
5287 |
(not (org-element--cache-key-less-p key start))) |
|
5288 |
(progn (push node stack) |
|
5289 |
(setq node (avl-tree--node-left node))) |
|
5290 |
(unless (org-element--cache-key-less-p key start) |
|
5291 |
;; We reached NEXT. Request is complete. |
|
5292 |
(when (equal key next) (throw 'quit t)) |
|
5293 |
;; Handle interruption request. Update current request. |
|
5294 |
(when (or exit-flag (org-element--cache-interrupt-p time-limit)) |
|
5295 |
(aset request 0 key) |
|
5296 |
(aset request 4 parent) |
|
5297 |
(throw 'interrupt nil)) |
|
5298 |
;; Shift element. |
|
5299 |
(unless (zerop offset) |
|
5300 |
(org-element--cache-shift-positions data offset)) |
|
5301 |
(let ((begin (org-element-property :begin data))) |
|
5302 |
;; Update PARENT and re-parent DATA, only when |
|
5303 |
;; necessary. Propagate new structures for lists. |
|
5304 |
(while (and parent |
|
5305 |
(<= (org-element-property :end parent) begin)) |
|
5306 |
(setq parent (org-element-property :parent parent))) |
|
5307 |
(cond ((and (not parent) (zerop offset)) (throw 'quit nil)) |
|
5308 |
((and parent |
|
5309 |
(let ((p (org-element-property :parent data))) |
|
5310 |
(or (not p) |
|
5311 |
(< (org-element-property :begin p) |
|
5312 |
(org-element-property :begin parent))))) |
|
5313 |
(org-element-put-property data :parent parent) |
|
5314 |
(let ((s (org-element-property :structure parent))) |
|
5315 |
(when (and s (org-element-property :structure data)) |
|
5316 |
(org-element-put-property data :structure s))))) |
|
5317 |
;; Cache is up-to-date past THRESHOLD. Request |
|
5318 |
;; interruption. |
|
5319 |
(when (and threshold (> begin threshold)) (setq exit-flag t)))) |
|
5320 |
(setq node (if (setq leftp (avl-tree--node-right node)) |
|
5321 |
(avl-tree--node-right node) |
|
5322 |
(pop stack)))))) |
|
5323 |
;; We reached end of tree: synchronization complete. |
|
5324 |
t))) |
|
5325 |
|
|
5326 |
(defun org-element--parse-to (pos &optional syncp time-limit) |
|
5327 |
"Parse elements in current section, down to POS. |
|
5328 |
|
|
5329 |
Start parsing from the closest between the last known element in |
|
5330 |
cache or headline above. Return the smallest element containing |
|
5331 |
POS. |
|
5332 |
|
|
5333 |
When optional argument SYNCP is non-nil, return the parent of the |
|
5334 |
element containing POS instead. In that case, it is also |
|
5335 |
possible to provide TIME-LIMIT, which is a time value specifying |
|
5336 |
when the parsing should stop. The function throws `interrupt' if |
|
5337 |
the process stopped before finding the expected result." |
|
5338 |
(catch 'exit |
|
5339 |
(org-with-wide-buffer |
|
5340 |
(goto-char pos) |
|
5341 |
(let* ((cached (and (org-element--cache-active-p) |
|
5342 |
(org-element--cache-find pos nil))) |
|
5343 |
(begin (org-element-property :begin cached)) |
|
5344 |
element next mode) |
|
5345 |
(cond |
|
5346 |
;; Nothing in cache before point: start parsing from first |
|
5347 |
;; element following headline above, or first element in |
|
5348 |
;; buffer. |
|
5349 |
((not cached) |
|
5350 |
(when (org-with-limited-levels (outline-previous-heading)) |
|
5351 |
(setq mode 'planning) |
|
5352 |
(forward-line)) |
|
5353 |
(skip-chars-forward " \r\t\n") |
|
5354 |
(beginning-of-line)) |
|
5355 |
;; Cache returned exact match: return it. |
|
5356 |
((= pos begin) |
|
5357 |
(throw 'exit (if syncp (org-element-property :parent cached) cached))) |
|
5358 |
;; There's a headline between cached value and POS: cached |
|
5359 |
;; value is invalid. Start parsing from first element |
|
5360 |
;; following the headline. |
|
5361 |
((re-search-backward |
|
5362 |
(org-with-limited-levels org-outline-regexp-bol) begin t) |
|
5363 |
(forward-line) |
|
5364 |
(skip-chars-forward " \r\t\n") |
|
5365 |
(beginning-of-line) |
|
5366 |
(setq mode 'planning)) |
|
5367 |
;; Check if CACHED or any of its ancestors contain point. |
|
5368 |
;; |
|
5369 |
;; If there is such an element, we inspect it in order to know |
|
5370 |
;; if we return it or if we need to parse its contents. |
|
5371 |
;; Otherwise, we just start parsing from current location, |
|
5372 |
;; which is right after the top-most element containing |
|
5373 |
;; CACHED. |
|
5374 |
;; |
|
5375 |
;; As a special case, if POS is at the end of the buffer, we |
|
5376 |
;; want to return the innermost element ending there. |
|
5377 |
;; |
|
5378 |
;; Also, if we find an ancestor and discover that we need to |
|
5379 |
;; parse its contents, make sure we don't start from |
|
5380 |
;; `:contents-begin', as we would otherwise go past CACHED |
|
5381 |
;; again. Instead, in that situation, we will resume parsing |
|
5382 |
;; from NEXT, which is located after CACHED or its higher |
|
5383 |
;; ancestor not containing point. |
|
5384 |
(t |
|
5385 |
(let ((up cached) |
|
5386 |
(pos (if (= (point-max) pos) (1- pos) pos))) |
|
5387 |
(goto-char (or (org-element-property :contents-begin cached) begin)) |
|
5388 |
(while (let ((end (org-element-property :end up))) |
|
5389 |
(and (<= end pos) |
|
5390 |
(goto-char end) |
|
5391 |
(setq up (org-element-property :parent up))))) |
|
5392 |
(cond ((not up)) |
|
5393 |
((eobp) (setq element up)) |
|
5394 |
(t (setq element up next (point))))))) |
|
5395 |
;; Parse successively each element until we reach POS. |
|
5396 |
(let ((end (or (org-element-property :end element) |
|
5397 |
(save-excursion |
|
5398 |
(org-with-limited-levels (outline-next-heading)) |
|
5399 |
(point)))) |
|
5400 |
(parent element)) |
|
5401 |
(while t |
|
5402 |
(when syncp |
|
5403 |
(cond ((= (point) pos) (throw 'exit parent)) |
|
5404 |
((org-element--cache-interrupt-p time-limit) |
|
5405 |
(throw 'interrupt nil)))) |
|
5406 |
(unless element |
|
5407 |
(setq element (org-element--current-element |
|
5408 |
end 'element mode |
|
5409 |
(org-element-property :structure parent))) |
|
5410 |
(org-element-put-property element :parent parent) |
|
5411 |
(org-element--cache-put element)) |
|
5412 |
(let ((elem-end (org-element-property :end element)) |
|
5413 |
(type (org-element-type element))) |
|
5414 |
(cond |
|
5415 |
;; Skip any element ending before point. Also skip |
|
5416 |
;; element ending at point (unless it is also the end of |
|
5417 |
;; buffer) since we're sure that another element begins |
|
5418 |
;; after it. |
|
5419 |
((and (<= elem-end pos) (/= (point-max) elem-end)) |
|
5420 |
(goto-char elem-end) |
|
5421 |
(setq mode (org-element--next-mode type nil))) |
|
5422 |
;; A non-greater element contains point: return it. |
|
5423 |
((not (memq type org-element-greater-elements)) |
|
5424 |
(throw 'exit element)) |
|
5425 |
;; Otherwise, we have to decide if ELEMENT really |
|
5426 |
;; contains POS. In that case we start parsing from |
|
5427 |
;; contents' beginning. |
|
5428 |
;; |
|
5429 |
;; If POS is at contents' beginning but it is also at |
|
5430 |
;; the beginning of the first item in a list or a table. |
|
5431 |
;; In that case, we need to create an anchor for that |
|
5432 |
;; list or table, so return it. |
|
5433 |
;; |
|
5434 |
;; Also, if POS is at the end of the buffer, no element |
|
5435 |
;; can start after it, but more than one may end there. |
|
5436 |
;; Arbitrarily, we choose to return the innermost of |
|
5437 |
;; such elements. |
|
5438 |
((let ((cbeg (org-element-property :contents-begin element)) |
|
5439 |
(cend (org-element-property :contents-end element))) |
|
5440 |
(when (or syncp |
|
5441 |
(and cbeg cend |
|
5442 |
(or (< cbeg pos) |
|
5443 |
(and (= cbeg pos) |
|
5444 |
(not (memq type '(plain-list table))))) |
|
5445 |
(or (> cend pos) |
|
5446 |
(and (= cend pos) (= (point-max) pos))))) |
|
5447 |
(goto-char (or next cbeg)) |
|
5448 |
(setq next nil |
|
5449 |
mode (org-element--next-mode type t) |
|
5450 |
parent element |
|
5451 |
end cend)))) |
|
5452 |
;; Otherwise, return ELEMENT as it is the smallest |
|
5453 |
;; element containing POS. |
|
5454 |
(t (throw 'exit element)))) |
|
5455 |
(setq element nil))))))) |
|
5456 |
|
|
5457 |
|
|
5458 |
;;;; Staging Buffer Changes |
|
5459 |
|
|
5460 |
(defconst org-element--cache-sensitive-re |
|
5461 |
(concat |
|
5462 |
org-outline-regexp-bol "\\|" |
|
5463 |
"\\\\end{[A-Za-z0-9*]+}[ \t]*$" "\\|" |
|
5464 |
"^[ \t]*\\(?:" |
|
5465 |
"#\\+\\(?:BEGIN[:_]\\|END\\(?:_\\|:?[ \t]*$\\)\\)" "\\|" |
|
5466 |
"\\\\begin{[A-Za-z0-9*]+}" "\\|" |
|
5467 |
":\\(?:\\w\\|[-_]\\)+:[ \t]*$" |
|
5468 |
"\\)") |
|
5469 |
"Regexp matching a sensitive line, structure wise. |
|
5470 |
A sensitive line is a headline, inlinetask, block, drawer, or |
|
5471 |
latex-environment boundary. When such a line is modified, |
|
5472 |
structure changes in the document may propagate in the whole |
|
5473 |
section, possibly making cache invalid.") |
|
5474 |
|
|
5475 |
(defvar org-element--cache-change-warning nil |
|
5476 |
"Non-nil when a sensitive line is about to be changed. |
|
5477 |
It is a symbol among nil, t and `headline'.") |
|
5478 |
|
|
5479 |
(defun org-element--cache-before-change (beg end) |
|
5480 |
"Request extension of area going to be modified if needed. |
|
5481 |
BEG and END are the beginning and end of the range of changed |
|
5482 |
text. See `before-change-functions' for more information." |
|
5483 |
(when (org-element--cache-active-p) |
|
5484 |
(org-with-wide-buffer |
|
5485 |
(goto-char beg) |
|
5486 |
(beginning-of-line) |
|
5487 |
(let ((bottom (save-excursion (goto-char end) (line-end-position)))) |
|
5488 |
(setq org-element--cache-change-warning |
|
5489 |
(save-match-data |
|
5490 |
(if (and (org-with-limited-levels (org-at-heading-p)) |
|
5491 |
(= (line-end-position) bottom)) |
|
5492 |
'headline |
|
5493 |
(let ((case-fold-search t)) |
|
5494 |
(re-search-forward |
|
5495 |
org-element--cache-sensitive-re bottom t))))))))) |
|
5496 |
|
|
5497 |
(defun org-element--cache-after-change (beg end pre) |
|
5498 |
"Update buffer modifications for current buffer. |
|
5499 |
BEG and END are the beginning and end of the range of changed |
|
5500 |
text, and the length in bytes of the pre-change text replaced by |
|
5501 |
that range. See `after-change-functions' for more information." |
|
5502 |
(when (org-element--cache-active-p) |
|
5503 |
(org-with-wide-buffer |
|
5504 |
(goto-char beg) |
|
5505 |
(beginning-of-line) |
|
5506 |
(save-match-data |
|
5507 |
(let ((top (point)) |
|
5508 |
(bottom (save-excursion (goto-char end) (line-end-position)))) |
|
5509 |
;; Determine if modified area needs to be extended, according |
|
5510 |
;; to both previous and current state. We make a special |
|
5511 |
;; case for headline editing: if a headline is modified but |
|
5512 |
;; not removed, do not extend. |
|
5513 |
(when (pcase org-element--cache-change-warning |
|
5514 |
(`t t) |
|
5515 |
(`headline |
|
5516 |
(not (and (org-with-limited-levels (org-at-heading-p)) |
|
5517 |
(= (line-end-position) bottom)))) |
|
5518 |
(_ |
|
5519 |
(let ((case-fold-search t)) |
|
5520 |
(re-search-forward |
|
5521 |
org-element--cache-sensitive-re bottom t)))) |
|
5522 |
;; Effectively extend modified area. |
|
5523 |
(org-with-limited-levels |
|
5524 |
(setq top (progn (goto-char top) |
|
5525 |
(when (outline-previous-heading) (forward-line)) |
|
5526 |
(point))) |
|
5527 |
(setq bottom (progn (goto-char bottom) |
|
5528 |
(if (outline-next-heading) (1- (point)) |
|
5529 |
(point)))))) |
|
5530 |
;; Store synchronization request. |
|
5531 |
(let ((offset (- end beg pre))) |
|
5532 |
(org-element--cache-submit-request top (- bottom offset) offset))))) |
|
5533 |
;; Activate a timer to process the request during idle time. |
|
5534 |
(org-element--cache-set-timer (current-buffer)))) |
|
5535 |
|
|
5536 |
(defun org-element--cache-for-removal (beg end offset) |
|
5537 |
"Return first element to remove from cache. |
|
5538 |
|
|
5539 |
BEG and END are buffer positions delimiting buffer modifications. |
|
5540 |
OFFSET is the size of the changes. |
|
5541 |
|
|
5542 |
Returned element is usually the first element in cache containing |
|
5543 |
any position between BEG and END. As an exception, greater |
|
5544 |
elements around the changes that are robust to contents |
|
5545 |
modifications are preserved and updated according to the |
|
5546 |
changes." |
|
5547 |
(let* ((elements (org-element--cache-find (1- beg) 'both)) |
|
5548 |
(before (car elements)) |
|
5549 |
(after (cdr elements))) |
|
5550 |
(if (not before) after |
|
5551 |
(let ((up before) |
|
5552 |
(robust-flag t)) |
|
5553 |
(while up |
|
5554 |
(if (let ((type (org-element-type up))) |
|
5555 |
(and (or (memq type '(center-block dynamic-block quote-block |
|
5556 |
special-block)) |
|
5557 |
;; Drawers named "PROPERTIES" are probably |
|
5558 |
;; a properties drawer being edited. Force |
|
5559 |
;; parsing to check if editing is over. |
|
5560 |
(and (eq type 'drawer) |
|
5561 |
(not (string= |
|
5562 |
(org-element-property :drawer-name up) |
|
5563 |
"PROPERTIES")))) |
|
5564 |
(let ((cbeg (org-element-property :contents-begin up))) |
|
5565 |
(and cbeg |
|
5566 |
(<= cbeg beg) |
|
5567 |
(> (org-element-property :contents-end up) end))))) |
|
5568 |
;; UP is a robust greater element containing changes. |
|
5569 |
;; We only need to extend its ending boundaries. |
|
5570 |
(org-element--cache-shift-positions |
|
5571 |
up offset '(:contents-end :end)) |
|
5572 |
(setq before up) |
|
5573 |
(when robust-flag (setq robust-flag nil))) |
|
5574 |
(setq up (org-element-property :parent up))) |
|
5575 |
;; We're at top level element containing ELEMENT: if it's |
|
5576 |
;; altered by buffer modifications, it is first element in |
|
5577 |
;; cache to be removed. Otherwise, that first element is the |
|
5578 |
;; following one. |
|
5579 |
;; |
|
5580 |
;; As a special case, do not remove BEFORE if it is a robust |
|
5581 |
;; container for current changes. |
|
5582 |
(if (or (< (org-element-property :end before) beg) robust-flag) after |
|
5583 |
before))))) |
|
5584 |
|
|
5585 |
(defun org-element--cache-submit-request (beg end offset) |
|
5586 |
"Submit a new cache synchronization request for current buffer. |
|
5587 |
BEG and END are buffer positions delimiting the minimal area |
|
5588 |
where cache data should be removed. OFFSET is the size of the |
|
5589 |
change, as an integer." |
|
5590 |
(let ((next (car org-element--cache-sync-requests)) |
|
5591 |
delete-to delete-from) |
|
5592 |
(if (and next |
|
5593 |
(zerop (aref next 5)) |
|
5594 |
(> (setq delete-to (+ (aref next 2) (aref next 3))) end) |
|
5595 |
(<= (setq delete-from (aref next 1)) end)) |
|
5596 |
;; Current changes can be merged with first sync request: we |
|
5597 |
;; can save a partial cache synchronization. |
|
5598 |
(progn |
|
5599 |
(cl-incf (aref next 3) offset) |
|
5600 |
;; If last change happened within area to be removed, extend |
|
5601 |
;; boundaries of robust parents, if any. Otherwise, find |
|
5602 |
;; first element to remove and update request accordingly. |
|
5603 |
(if (> beg delete-from) |
|
5604 |
(let ((up (aref next 4))) |
|
5605 |
(while up |
|
5606 |
(org-element--cache-shift-positions |
|
5607 |
up offset '(:contents-end :end)) |
|
5608 |
(setq up (org-element-property :parent up)))) |
|
5609 |
(let ((first (org-element--cache-for-removal beg delete-to offset))) |
|
5610 |
(when first |
|
5611 |
(aset next 0 (org-element--cache-key first)) |
|
5612 |
(aset next 1 (org-element-property :begin first)) |
|
5613 |
(aset next 4 (org-element-property :parent first)))))) |
|
5614 |
;; Ensure cache is correct up to END. Also make sure that NEXT, |
|
5615 |
;; if any, is no longer a 0-phase request, thus ensuring that |
|
5616 |
;; phases are properly ordered. We need to provide OFFSET as |
|
5617 |
;; optional parameter since current modifications are not known |
|
5618 |
;; yet to the otherwise correct part of the cache (i.e, before |
|
5619 |
;; the first request). |
|
5620 |
(when next (org-element--cache-sync (current-buffer) end beg)) |
|
5621 |
(let ((first (org-element--cache-for-removal beg end offset))) |
|
5622 |
(if first |
|
5623 |
(push (let ((beg (org-element-property :begin first)) |
|
5624 |
(key (org-element--cache-key first))) |
|
5625 |
(cond |
|
5626 |
;; When changes happen before the first known |
|
5627 |
;; element, re-parent and shift the rest of the |
|
5628 |
;; cache. |
|
5629 |
((> beg end) (vector key beg nil offset nil 1)) |
|
5630 |
;; Otherwise, we find the first non robust |
|
5631 |
;; element containing END. All elements between |
|
5632 |
;; FIRST and this one are to be removed. |
|
5633 |
((let ((first-end (org-element-property :end first))) |
|
5634 |
(and (> first-end end) |
|
5635 |
(vector key beg first-end offset first 0)))) |
|
5636 |
(t |
|
5637 |
(let* ((element (org-element--cache-find end)) |
|
5638 |
(end (org-element-property :end element)) |
|
5639 |
(up element)) |
|
5640 |
(while (and (setq up (org-element-property :parent up)) |
|
5641 |
(>= (org-element-property :begin up) beg)) |
|
5642 |
(setq end (org-element-property :end up) |
|
5643 |
element up)) |
|
5644 |
(vector key beg end offset element 0))))) |
|
5645 |
org-element--cache-sync-requests) |
|
5646 |
;; No element to remove. No need to re-parent either. |
|
5647 |
;; Simply shift additional elements, if any, by OFFSET. |
|
5648 |
(when org-element--cache-sync-requests |
|
5649 |
(cl-incf (aref (car org-element--cache-sync-requests) 3) |
|
5650 |
offset))))))) |
|
5651 |
|
|
5652 |
|
|
5653 |
;;;; Public Functions |
|
5654 |
|
|
5655 |
;;;###autoload |
|
5656 |
(defun org-element-cache-reset (&optional all) |
|
5657 |
"Reset cache in current buffer. |
|
5658 |
When optional argument ALL is non-nil, reset cache in all Org |
|
5659 |
buffers." |
|
5660 |
(interactive "P") |
|
5661 |
(dolist (buffer (if all (buffer-list) (list (current-buffer)))) |
|
5662 |
(with-current-buffer buffer |
|
5663 |
(when (and org-element-use-cache (derived-mode-p 'org-mode)) |
|
5664 |
(setq-local org-element--cache |
|
5665 |
(avl-tree-create #'org-element--cache-compare)) |
|
5666 |
(setq-local org-element--cache-sync-keys |
|
5667 |
(make-hash-table :weakness 'key :test #'eq)) |
|
5668 |
(setq-local org-element--cache-change-warning nil) |
|
5669 |
(setq-local org-element--cache-sync-requests nil) |
|
5670 |
(setq-local org-element--cache-sync-timer nil) |
|
5671 |
(add-hook 'before-change-functions |
|
5672 |
#'org-element--cache-before-change nil t) |
|
5673 |
(add-hook 'after-change-functions |
|
5674 |
#'org-element--cache-after-change nil t))))) |
|
5675 |
|
|
5676 |
;;;###autoload |
|
5677 |
(defun org-element-cache-refresh (pos) |
|
5678 |
"Refresh cache at position POS." |
|
5679 |
(when (org-element--cache-active-p) |
|
5680 |
(org-element--cache-sync (current-buffer) pos) |
|
5681 |
(org-element--cache-submit-request pos pos 0) |
|
5682 |
(org-element--cache-set-timer (current-buffer)))) |
|
5683 |
|
|
5684 |
|
|
5685 |
|
|
5686 |
;;; The Toolbox |
|
5687 |
;; |
|
5688 |
;; The first move is to implement a way to obtain the smallest element |
|
5689 |
;; containing point. This is the job of `org-element-at-point'. It |
|
5690 |
;; basically jumps back to the beginning of section containing point |
|
5691 |
;; and proceed, one element after the other, with |
|
5692 |
;; `org-element--current-element' until the container is found. Note: |
|
5693 |
;; When using `org-element-at-point', secondary values are never |
|
5694 |
;; parsed since the function focuses on elements, not on objects. |
|
5695 |
;; |
|
5696 |
;; At a deeper level, `org-element-context' lists all elements and |
|
5697 |
;; objects containing point. |
|
5698 |
;; |
|
5699 |
;; `org-element-nested-p' and `org-element-swap-A-B' may be used |
|
5700 |
;; internally by navigation and manipulation tools. |
|
5701 |
|
|
5702 |
|
|
5703 |
;;;###autoload |
|
5704 |
(defun org-element-at-point () |
|
5705 |
"Determine closest element around point. |
|
5706 |
|
|
5707 |
Return value is a list like (TYPE PROPS) where TYPE is the type |
|
5708 |
of the element and PROPS a plist of properties associated to the |
|
5709 |
element. |
|
5710 |
|
|
5711 |
Possible types are defined in `org-element-all-elements'. |
|
5712 |
Properties depend on element or object type, but always include |
|
5713 |
`:begin', `:end', `:parent' and `:post-blank' properties. |
|
5714 |
|
|
5715 |
As a special case, if point is at the very beginning of the first |
|
5716 |
item in a list or sub-list, returned element will be that list |
|
5717 |
instead of the item. Likewise, if point is at the beginning of |
|
5718 |
the first row of a table, returned element will be the table |
|
5719 |
instead of the first row. |
|
5720 |
|
|
5721 |
When point is at the end of the buffer, return the innermost |
|
5722 |
element ending there." |
|
5723 |
(org-with-wide-buffer |
|
5724 |
(let ((origin (point))) |
|
5725 |
(end-of-line) |
|
5726 |
(skip-chars-backward " \r\t\n") |
|
5727 |
(cond |
|
5728 |
;; Within blank lines at the beginning of buffer, return nil. |
|
5729 |
((bobp) nil) |
|
5730 |
;; Within blank lines right after a headline, return that |
|
5731 |
;; headline. |
|
5732 |
((org-with-limited-levels (org-at-heading-p)) |
|
5733 |
(beginning-of-line) |
|
5734 |
(org-element-headline-parser (point-max) t)) |
|
5735 |
;; Otherwise parse until we find element containing ORIGIN. |
|
5736 |
(t |
|
5737 |
(when (org-element--cache-active-p) |
|
5738 |
(if (not org-element--cache) (org-element-cache-reset) |
|
5739 |
(org-element--cache-sync (current-buffer) origin))) |
|
5740 |
(org-element--parse-to origin)))))) |
|
5741 |
|
|
5742 |
;;;###autoload |
|
5743 |
(defun org-element-context (&optional element) |
|
5744 |
"Return smallest element or object around point. |
|
5745 |
|
|
5746 |
Return value is a list like (TYPE PROPS) where TYPE is the type |
|
5747 |
of the element or object and PROPS a plist of properties |
|
5748 |
associated to it. |
|
5749 |
|
|
5750 |
Possible types are defined in `org-element-all-elements' and |
|
5751 |
`org-element-all-objects'. Properties depend on element or |
|
5752 |
object type, but always include `:begin', `:end', `:parent' and |
|
5753 |
`:post-blank'. |
|
5754 |
|
|
5755 |
As a special case, if point is right after an object and not at |
|
5756 |
the beginning of any other object, return that object. |
|
5757 |
|
|
5758 |
Optional argument ELEMENT, when non-nil, is the closest element |
|
5759 |
containing point, as returned by `org-element-at-point'. |
|
5760 |
Providing it allows for quicker computation." |
|
5761 |
(catch 'objects-forbidden |
|
5762 |
(org-with-wide-buffer |
|
5763 |
(let* ((pos (point)) |
|
5764 |
(element (or element (org-element-at-point))) |
|
5765 |
(type (org-element-type element)) |
|
5766 |
(post (org-element-property :post-affiliated element))) |
|
5767 |
;; If point is inside an element containing objects or |
|
5768 |
;; a secondary string, narrow buffer to the container and |
|
5769 |
;; proceed with parsing. Otherwise, return ELEMENT. |
|
5770 |
(cond |
|
5771 |
;; At a parsed affiliated keyword, check if we're inside main |
|
5772 |
;; or dual value. |
|
5773 |
((and post (< pos post)) |
|
5774 |
(beginning-of-line) |
|
5775 |
(let ((case-fold-search t)) (looking-at org-element--affiliated-re)) |
|
5776 |
(cond |
|
5777 |
((not (member-ignore-case (match-string 1) |
|
5778 |
org-element-parsed-keywords)) |
|
5779 |
(throw 'objects-forbidden element)) |
|
5780 |
((< (match-end 0) pos) |
|
5781 |
(narrow-to-region (match-end 0) (line-end-position))) |
|
5782 |
((and (match-beginning 2) |
|
5783 |
(>= pos (match-beginning 2)) |
|
5784 |
(< pos (match-end 2))) |
|
5785 |
(narrow-to-region (match-beginning 2) (match-end 2))) |
|
5786 |
(t (throw 'objects-forbidden element))) |
|
5787 |
;; Also change type to retrieve correct restrictions. |
|
5788 |
(setq type 'keyword)) |
|
5789 |
;; At an item, objects can only be located within tag, if any. |
|
5790 |
((eq type 'item) |
|
5791 |
(let ((tag (org-element-property :tag element))) |
|
5792 |
(if (or (not tag) (/= (line-beginning-position) post)) |
|
5793 |
(throw 'objects-forbidden element) |
|
5794 |
(beginning-of-line) |
|
5795 |
(search-forward tag (line-end-position)) |
|
5796 |
(goto-char (match-beginning 0)) |
|
5797 |
(if (and (>= pos (point)) (< pos (match-end 0))) |
|
5798 |
(narrow-to-region (point) (match-end 0)) |
|
5799 |
(throw 'objects-forbidden element))))) |
|
5800 |
;; At an headline or inlinetask, objects are in title. |
|
5801 |
((memq type '(headline inlinetask)) |
|
5802 |
(let ((case-fold-search nil)) |
|
5803 |
(goto-char (org-element-property :begin element)) |
|
5804 |
(looking-at org-complex-heading-regexp) |
|
5805 |
(let ((end (match-end 4))) |
|
5806 |
(if (not end) (throw 'objects-forbidden element) |
|
5807 |
(goto-char (match-beginning 4)) |
|
5808 |
(when (looking-at org-comment-string) |
|
5809 |
(goto-char (match-end 0))) |
|
5810 |
(if (>= (point) end) (throw 'objects-forbidden element) |
|
5811 |
(narrow-to-region (point) end)))))) |
|
5812 |
;; At a paragraph, a table-row or a verse block, objects are |
|
5813 |
;; located within their contents. |
|
5814 |
((memq type '(paragraph table-row verse-block)) |
|
5815 |
(let ((cbeg (org-element-property :contents-begin element)) |
|
5816 |
(cend (org-element-property :contents-end element))) |
|
5817 |
;; CBEG is nil for table rules. |
|
5818 |
(if (and cbeg cend (>= pos cbeg) |
|
5819 |
(or (< pos cend) (and (= pos cend) (eobp)))) |
|
5820 |
(narrow-to-region cbeg cend) |
|
5821 |
(throw 'objects-forbidden element)))) |
|
5822 |
(t (throw 'objects-forbidden element))) |
|
5823 |
(goto-char (point-min)) |
|
5824 |
(let ((restriction (org-element-restriction type)) |
|
5825 |
(parent element) |
|
5826 |
last) |
|
5827 |
(catch 'exit |
|
5828 |
(while t |
|
5829 |
(let ((next (org-element--object-lex restriction))) |
|
5830 |
(when next (org-element-put-property next :parent parent)) |
|
5831 |
;; Process NEXT, if any, in order to know if we need to |
|
5832 |
;; skip it, return it or move into it. |
|
5833 |
(if (or (not next) (> (org-element-property :begin next) pos)) |
|
5834 |
(throw 'exit (or last parent)) |
|
5835 |
(let ((end (org-element-property :end next)) |
|
5836 |
(cbeg (org-element-property :contents-begin next)) |
|
5837 |
(cend (org-element-property :contents-end next))) |
|
5838 |
(cond |
|
5839 |
;; Skip objects ending before point. Also skip |
|
5840 |
;; objects ending at point unless it is also the |
|
5841 |
;; end of buffer, since we want to return the |
|
5842 |
;; innermost object. |
|
5843 |
((and (<= end pos) (/= (point-max) end)) |
|
5844 |
(goto-char end) |
|
5845 |
;; For convenience, when object ends at POS, |
|
5846 |
;; without any space, store it in LAST, as we |
|
5847 |
;; will return it if no object starts here. |
|
5848 |
(when (and (= end pos) |
|
5849 |
(not (memq (char-before) '(?\s ?\t)))) |
|
5850 |
(setq last next))) |
|
5851 |
;; If POS is within a container object, move into |
|
5852 |
;; that object. |
|
5853 |
((and cbeg cend |
|
5854 |
(>= pos cbeg) |
|
5855 |
(or (< pos cend) |
|
5856 |
;; At contents' end, if there is no |
|
5857 |
;; space before point, also move into |
|
5858 |
;; object, for consistency with |
|
5859 |
;; convenience feature above. |
|
5860 |
(and (= pos cend) |
|
5861 |
(or (= (point-max) pos) |
|
5862 |
(not (memq (char-before pos) |
|
5863 |
'(?\s ?\t))))))) |
|
5864 |
(goto-char cbeg) |
|
5865 |
(narrow-to-region (point) cend) |
|
5866 |
(setq parent next) |
|
5867 |
(setq restriction (org-element-restriction next))) |
|
5868 |
;; Otherwise, return NEXT. |
|
5869 |
(t (throw 'exit next))))))))))))) |
|
5870 |
|
|
5871 |
(defun org-element-lineage (blob &optional types with-self) |
|
5872 |
"List all ancestors of a given element or object. |
|
5873 |
|
|
5874 |
BLOB is an object or element. |
|
5875 |
|
|
5876 |
When optional argument TYPES is a list of symbols, return the |
|
5877 |
first element or object in the lineage whose type belongs to that |
|
5878 |
list. |
|
5879 |
|
|
5880 |
When optional argument WITH-SELF is non-nil, lineage includes |
|
5881 |
BLOB itself as the first element, and TYPES, if provided, also |
|
5882 |
apply to it. |
|
5883 |
|
|
5884 |
When BLOB is obtained through `org-element-context' or |
|
5885 |
`org-element-at-point', only ancestors from its section can be |
|
5886 |
found. There is no such limitation when BLOB belongs to a full |
|
5887 |
parse tree." |
|
5888 |
(let ((up (if with-self blob (org-element-property :parent blob))) |
|
5889 |
ancestors) |
|
5890 |
(while (and up (not (memq (org-element-type up) types))) |
|
5891 |
(unless types (push up ancestors)) |
|
5892 |
(setq up (org-element-property :parent up))) |
|
5893 |
(if types up (nreverse ancestors)))) |
|
5894 |
|
|
5895 |
(defun org-element-nested-p (elem-A elem-B) |
|
5896 |
"Non-nil when elements ELEM-A and ELEM-B are nested." |
|
5897 |
(let ((beg-A (org-element-property :begin elem-A)) |
|
5898 |
(beg-B (org-element-property :begin elem-B)) |
|
5899 |
(end-A (org-element-property :end elem-A)) |
|
5900 |
(end-B (org-element-property :end elem-B))) |
|
5901 |
(or (and (>= beg-A beg-B) (<= end-A end-B)) |
|
5902 |
(and (>= beg-B beg-A) (<= end-B end-A))))) |
|
5903 |
|
|
5904 |
(defun org-element-swap-A-B (elem-A elem-B) |
|
5905 |
"Swap elements ELEM-A and ELEM-B. |
|
5906 |
Assume ELEM-B is after ELEM-A in the buffer. Leave point at the |
|
5907 |
end of ELEM-A." |
|
5908 |
(goto-char (org-element-property :begin elem-A)) |
|
5909 |
;; There are two special cases when an element doesn't start at bol: |
|
5910 |
;; the first paragraph in an item or in a footnote definition. |
|
5911 |
(let ((specialp (not (bolp)))) |
|
5912 |
;; Only a paragraph without any affiliated keyword can be moved at |
|
5913 |
;; ELEM-A position in such a situation. Note that the case of |
|
5914 |
;; a footnote definition is impossible: it cannot contain two |
|
5915 |
;; paragraphs in a row because it cannot contain a blank line. |
|
5916 |
(if (and specialp |
|
5917 |
(or (not (eq (org-element-type elem-B) 'paragraph)) |
|
5918 |
(/= (org-element-property :begin elem-B) |
|
5919 |
(org-element-property :contents-begin elem-B)))) |
|
5920 |
(error "Cannot swap elements")) |
|
5921 |
;; In a special situation, ELEM-A will have no indentation. We'll |
|
5922 |
;; give it ELEM-B's (which will in, in turn, have no indentation). |
|
5923 |
(let* ((ind-B (when specialp |
|
5924 |
(goto-char (org-element-property :begin elem-B)) |
|
5925 |
(org-get-indentation))) |
|
5926 |
(beg-A (org-element-property :begin elem-A)) |
|
5927 |
(end-A (save-excursion |
|
5928 |
(goto-char (org-element-property :end elem-A)) |
|
5929 |
(skip-chars-backward " \r\t\n") |
|
5930 |
(point-at-eol))) |
|
5931 |
(beg-B (org-element-property :begin elem-B)) |
|
5932 |
(end-B (save-excursion |
|
5933 |
(goto-char (org-element-property :end elem-B)) |
|
5934 |
(skip-chars-backward " \r\t\n") |
|
5935 |
(point-at-eol))) |
|
5936 |
;; Store inner overlays responsible for visibility status. |
|
5937 |
;; We also need to store their boundaries as they will be |
|
5938 |
;; removed from buffer. |
|
5939 |
(overlays |
|
5940 |
(cons |
|
5941 |
(delq nil |
|
5942 |
(mapcar (lambda (o) |
|
5943 |
(and (>= (overlay-start o) beg-A) |
|
5944 |
(<= (overlay-end o) end-A) |
|
5945 |
(list o (overlay-start o) (overlay-end o)))) |
|
5946 |
(overlays-in beg-A end-A))) |
|
5947 |
(delq nil |
|
5948 |
(mapcar (lambda (o) |
|
5949 |
(and (>= (overlay-start o) beg-B) |
|
5950 |
(<= (overlay-end o) end-B) |
|
5951 |
(list o (overlay-start o) (overlay-end o)))) |
|
5952 |
(overlays-in beg-B end-B))))) |
|
5953 |
;; Get contents. |
|
5954 |
(body-A (buffer-substring beg-A end-A)) |
|
5955 |
(body-B (delete-and-extract-region beg-B end-B))) |
|
5956 |
(goto-char beg-B) |
|
5957 |
(when specialp |
|
5958 |
(setq body-B (replace-regexp-in-string "\\`[ \t]*" "" body-B)) |
|
5959 |
(indent-to-column ind-B)) |
|
5960 |
(insert body-A) |
|
5961 |
;; Restore ex ELEM-A overlays. |
|
5962 |
(let ((offset (- beg-B beg-A))) |
|
5963 |
(dolist (o (car overlays)) |
|
5964 |
(move-overlay (car o) (+ (nth 1 o) offset) (+ (nth 2 o) offset))) |
|
5965 |
(goto-char beg-A) |
|
5966 |
(delete-region beg-A end-A) |
|
5967 |
(insert body-B) |
|
5968 |
;; Restore ex ELEM-B overlays. |
|
5969 |
(dolist (o (cdr overlays)) |
|
5970 |
(move-overlay (car o) (- (nth 1 o) offset) (- (nth 2 o) offset)))) |
|
5971 |
(goto-char (org-element-property :end elem-B))))) |
|
5972 |
|
|
5973 |
|
|
5974 |
(provide 'org-element) |
|
5975 |
|
|
5976 |
;; Local variables: |
|
5977 |
;; generated-autoload-file: "org-loaddefs.el" |
|
5978 |
;; End: |
|
5979 |
|
|
5980 |
;;; org-element.el ends here |