diff options
Diffstat (limited to 'lisp/org/org-element.el')
-rw-r--r-- | lisp/org/org-element.el | 268 |
1 files changed, 137 insertions, 131 deletions
diff --git a/lisp/org/org-element.el b/lisp/org/org-element.el index 4b5f9a19e6d..2ad557d2179 100644 --- a/lisp/org/org-element.el +++ b/lisp/org/org-element.el @@ -72,7 +72,6 @@ (declare-function org-at-heading-p "org" (&optional _)) (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) (declare-function org-escape-code-in-string "org-src" (s)) -(declare-function org-find-visible "org" ()) (declare-function org-macro-escape-arguments "org-macro" (&rest args)) (declare-function org-macro-extract-arguments "org-macro" (s)) (declare-function org-reduced-level "org" (l)) @@ -330,7 +329,9 @@ match group 2. Don't modify it, set `org-element-affiliated-keywords' instead.") (defconst org-element-object-restrictions - (let* ((standard-set (remq 'table-cell org-element-all-objects)) + (let* ((minimal-set '(bold code entity italic latex-fragment strike-through + subscript superscript underline verbatim)) + (standard-set (remq 'table-cell org-element-all-objects)) (standard-set-no-line-break (remq 'line-break standard-set))) `((bold ,@standard-set) (footnote-reference ,@standard-set) @@ -341,23 +342,20 @@ Don't modify it, set `org-element-affiliated-keywords' instead.") (keyword ,@(remq 'footnote-reference standard-set)) ;; Ignore all links in a link description. Also ignore ;; radio-targets and line breaks. - (link bold code entity export-snippet inline-babel-call inline-src-block - italic latex-fragment macro statistics-cookie strike-through - subscript superscript underline verbatim) + (link export-snippet inline-babel-call inline-src-block macro + statistics-cookie ,@minimal-set) (paragraph ,@standard-set) ;; Remove any variable object from radio target as it would ;; prevent it from being properly recognized. - (radio-target bold code entity italic latex-fragment strike-through - subscript superscript underline superscript) + (radio-target ,@minimal-set) (strike-through ,@standard-set) (subscript ,@standard-set) (superscript ,@standard-set) ;; Ignore inline babel call and inline source block as formulas ;; are possible. Also ignore line breaks and statistics ;; cookies. - (table-cell bold code entity export-snippet footnote-reference italic - latex-fragment link macro radio-target strike-through - subscript superscript target timestamp underline verbatim) + (table-cell export-snippet footnote-reference link macro radio-target + target timestamp ,@minimal-set) (table-row table-cell) (underline ,@standard-set) (verse-block ,@standard-set))) @@ -367,10 +365,6 @@ key is an element or object type containing objects and value is a list of types that can be contained within an element or object of such type. -For example, in a `radio-target' object, one can only find -entities, latex-fragments, subscript, superscript and text -markup. - This alist also applies to secondary string. For example, an `headline' type element doesn't directly contain objects, but still has an entry since one of its properties (`:title') does.") @@ -1806,13 +1800,10 @@ Return a list whose CAR is `clock' and CDR is a plist containing ;;;; Comment -(defun org-element-comment-parser (limit affiliated) +(defun org-element-comment-parser (limit) "Parse a comment. -LIMIT bounds the search. AFFILIATED is a list of which CAR is -the buffer position at the beginning of the first affiliated -keyword and CDR is a plist of affiliated keywords along with -their value. +LIMIT bounds the search. Return a list whose CAR is `comment' and CDR is a plist containing `:begin', `:end', `:value', `:post-blank', @@ -1820,8 +1811,7 @@ containing `:begin', `:end', `:value', `:post-blank', Assume point is at comment beginning." (save-excursion - (let* ((begin (car affiliated)) - (post-affiliated (point)) + (let* ((begin (point)) (value (prog2 (looking-at "[ \t]*# ?") (buffer-substring-no-properties (match-end 0) (line-end-position)) @@ -1843,13 +1833,11 @@ Assume point is at comment beginning." (skip-chars-forward " \r\t\n" limit) (if (eobp) (point) (line-beginning-position))))) (list 'comment - (nconc - (list :begin begin - :end end - :value value - :post-blank (count-lines com-end end) - :post-affiliated post-affiliated) - (cdr affiliated)))))) + (list :begin begin + :end end + :value value + :post-blank (count-lines com-end end) + :post-affiliated begin))))) (defun org-element-comment-interpreter (comment _) "Interpret COMMENT element as Org syntax. @@ -2186,9 +2174,9 @@ the buffer position at the beginning of the first affiliated keyword and CDR is a plist of affiliated keywords along with their value. -Return a list whose CAR is `keyword' and CDR is a plist -containing `:key', `:value', `:begin', `:end', `:post-blank' and -`:post-affiliated' keywords." +Return a list whose CAR is a normalized `keyword' (uppercase) and +CDR is a plist containing `:key', `:value', `:begin', `:end', +`:post-blank' and `:post-affiliated' keywords." (save-excursion ;; An orphaned affiliated keyword is considered as a regular ;; keyword. In this case AFFILIATED is nil, so we take care of @@ -3217,10 +3205,11 @@ Assume point is at the beginning of the link." (setq post-blank (progn (goto-char link-end) (skip-chars-forward " \t"))) (setq end (point))) - ;; Special "file" type link processing. Extract opening + ;; Special "file"-type link processing. Extract opening ;; application and search option, if any. Also normalize URI. (when (string-match "\\`file\\(?:\\+\\(.+\\)\\)?\\'" type) - (setq application (match-string 1 type) type "file") + (setq application (match-string 1 type)) + (setq type "file") (when (string-match "::\\(.*\\)\\'" path) (setq search-option (match-string 1 path)) (setq path (replace-match "" nil nil path))) @@ -3823,12 +3812,6 @@ Assume point is at the first equal sign marker." ;; `org-element--current-element' is the core function of this section. ;; It returns the Lisp representation of the element starting at ;; point. -;; -;; `org-element--current-element' makes use of special modes. They -;; are activated for fixed element chaining (e.g., `plain-list' > -;; `item') or fixed conditional element chaining (e.g., `headline' > -;; `section'). Special modes are: `first-section', `item', -;; `node-property', `section' and `table-row'. (defun org-element--current-element (limit &optional granularity mode structure) "Parse the element starting at point. @@ -3848,8 +3831,9 @@ nil), secondary values will not be parsed, since they only contain objects. Optional argument MODE, when non-nil, can be either -`first-section', `section', `planning', `item', `node-property' -and `table-row'. +`first-section', `item', `node-property', `planning', +`property-drawer', `section', `table-row', or `top-comment'. + If STRUCTURE isn't provided but MODE is set to `item', it will be computed. @@ -3879,15 +3863,22 @@ element it has to parse." (org-element-section-parser (or (save-excursion (org-with-limited-levels (outline-next-heading))) limit))) + ;; Comments. + ((looking-at "^[ \t]*#\\(?: \\|$\\)") + (org-element-comment-parser limit)) ;; Planning. ((and (eq mode 'planning) (eq ?* (char-after (line-beginning-position 0))) (looking-at org-planning-line-re)) (org-element-planning-parser limit)) ;; Property drawer. - ((and (memq mode '(planning property-drawer)) - (eq ?* (char-after (line-beginning-position - (if (eq mode 'planning) 0 -1)))) + ((and (pcase mode + (`planning (eq ?* (char-after (line-beginning-position 0)))) + ((or `property-drawer `top-comment) + (save-excursion + (beginning-of-line 0) + (not (looking-at "[[:blank:]]*$")))) + (_ nil)) (looking-at org-property-drawer-re)) (org-element-property-drawer-parser limit)) ;; When not at bol, point is at the beginning of an item or @@ -3896,7 +3887,7 @@ element it has to parse." ;; Clock. ((looking-at org-clock-line-re) (org-element-clock-parser limit)) ;; Inlinetask. - ((org-at-heading-p) + ((looking-at "^\\*+ ") (org-element-inlinetask-parser limit raw-secondary-p)) ;; From there, elements can have affiliated keywords. (t (let ((affiliated (org-element--collect-affiliated-keywords @@ -3910,7 +3901,7 @@ element it has to parse." ;; LaTeX Environment. ((looking-at org-element--latex-begin-environment) (org-element-latex-environment-parser limit affiliated)) - ;; Drawer and Property Drawer. + ;; Drawer. ((looking-at org-drawer-regexp) (org-element-drawer-parser limit affiliated)) ;; Fixed Width @@ -3918,13 +3909,10 @@ element it has to parse." (org-element-fixed-width-parser limit affiliated)) ;; Inline Comments, Blocks, Babel Calls, Dynamic Blocks and ;; Keywords. - ((looking-at "[ \t]*#") + ((looking-at "[ \t]*#\\+") (goto-char (match-end 0)) (cond - ((looking-at "\\(?: \\|$\\)") - (beginning-of-line) - (org-element-comment-parser limit affiliated)) - ((looking-at "\\+BEGIN_\\(\\S-+\\)") + ((looking-at "BEGIN_\\(\\S-+\\)") (beginning-of-line) (funcall (pcase (upcase (match-string 1)) ("CENTER" #'org-element-center-block-parser) @@ -3937,13 +3925,13 @@ element it has to parse." (_ #'org-element-special-block-parser)) limit affiliated)) - ((looking-at "\\+CALL:") + ((looking-at "CALL:") (beginning-of-line) (org-element-babel-call-parser limit affiliated)) - ((looking-at "\\+BEGIN:? ") + ((looking-at "BEGIN:? ") (beginning-of-line) (org-element-dynamic-block-parser limit affiliated)) - ((looking-at "\\+\\S-+:") + ((looking-at "\\S-+:") (beginning-of-line) (org-element-keyword-parser limit affiliated)) (t @@ -4024,7 +4012,8 @@ When PARSE is non-nil, values from keywords belonging to (skip-chars-backward " \t") (point)))) (if parsed? - (org-element--parse-objects beg end nil restrict) + (save-match-data + (org-element--parse-objects beg end nil restrict)) (org-trim (buffer-substring-no-properties beg end))))) ;; If KWD is a dual keyword, find its secondary value. ;; Maybe parse it. @@ -4144,7 +4133,9 @@ If STRING is the empty string or nil, return nil." (dolist (v local-variables) (ignore-errors (if (symbolp v) (makunbound v) - (set (make-local-variable (car v)) (cdr v))))) + ;; Don't set file name to avoid mishandling hooks (bug#44524) + (unless (memq (car v) '(buffer-file-name buffer-file-truename)) + (set (make-local-variable (car v)) (cdr v)))))) ;; Transferring local variables may put the temporary buffer ;; into a read-only state. Make sure we can insert STRING. (let ((inhibit-read-only t)) (insert string)) @@ -4320,34 +4311,41 @@ looking into captions: ;; `org-element--object-lex' to find the next object in the current ;; container. -(defsubst org-element--next-mode (type parentp) - "Return next special mode according to TYPE, or nil. -TYPE is a symbol representing the type of an element or object -containing next element if PARENTP is non-nil, or before it -otherwise. Modes can be either `first-section', `item', -`node-property', `planning', `property-drawer', `section', -`table-row' or nil." - (if parentp +(defsubst org-element--next-mode (mode type parent?) + "Return next mode according to current one. + +MODE is a symbol representing the expectation about the next +element or object. Meaningful values are `first-section', +`item', `node-property', `planning', `property-drawer', +`section', `table-row', `top-comment', and nil. + +TYPE is the type of the current element or object. + +If PARENT? is non-nil, assume the next element or object will be +located inside the current one. " + (if parent? (pcase type (`headline 'section) + ((and (guard (eq mode 'first-section)) `section) 'top-comment) (`inlinetask 'planning) (`plain-list 'item) (`property-drawer 'node-property) (`section 'planning) (`table 'table-row)) - (pcase type + (pcase mode (`item 'item) (`node-property 'node-property) - (`planning 'property-drawer) - (`table-row 'table-row)))) + ((and `planning (guard (eq type 'planning))) 'property-drawer) + (`table-row 'table-row) + ((and `top-comment (guard (eq type 'comment))) 'property-drawer)))) (defun org-element--parse-elements (beg end mode structure granularity visible-only acc) "Parse elements between BEG and END positions. MODE prioritizes some elements over the others. It can be set to -`first-section', `section', `planning', `item', `node-property' -or `table-row'. +`first-section', `item', `node-property', `planning', +`property-drawer', `section', `table-row', `top-comment', or nil. When value is `item', STRUCTURE will be used as the current list structure. @@ -4361,54 +4359,52 @@ elements. Elements are accumulated into ACC." (save-excursion (goto-char beg) - ;; Visible only: skip invisible parts at the beginning of the - ;; element. - (when (and visible-only (org-invisible-p2)) - (goto-char (min (1+ (org-find-visible)) end))) ;; When parsing only headlines, skip any text before first one. (when (and (eq granularity 'headline) (not (org-at-heading-p))) (org-with-limited-levels (outline-next-heading))) (let (elements) (while (< (point) end) - ;; Find current element's type and parse it accordingly to - ;; its category. - (let* ((element (org-element--current-element - end granularity mode structure)) - (type (org-element-type element)) - (cbeg (org-element-property :contents-begin element))) - (goto-char (org-element-property :end element)) - ;; Visible only: skip invisible parts between siblings. - (when (and visible-only (org-invisible-p2)) - (goto-char (min (1+ (org-find-visible)) end))) - ;; Fill ELEMENT contents by side-effect. - (cond - ;; If element has no contents, don't modify it. - ((not cbeg)) - ;; Greater element: parse it between `contents-begin' and - ;; `contents-end'. Make sure GRANULARITY allows the - ;; recursion, or ELEMENT is a headline, in which case going - ;; inside is mandatory, in order to get sub-level headings. - ((and (memq type org-element-greater-elements) - (or (memq granularity '(element object nil)) - (and (eq granularity 'greater-element) - (eq type 'section)) - (eq type 'headline))) - (org-element--parse-elements - cbeg (org-element-property :contents-end element) - ;; Possibly switch to a special mode. - (org-element--next-mode type t) - (and (memq type '(item plain-list)) - (org-element-property :structure element)) - granularity visible-only element)) - ;; ELEMENT has contents. Parse objects inside, if - ;; GRANULARITY allows it. - ((memq granularity '(object nil)) - (org-element--parse-objects - cbeg (org-element-property :contents-end element) element - (org-element-restriction type)))) - (push (org-element-put-property element :parent acc) elements) - ;; Update mode. - (setq mode (org-element--next-mode type nil)))) + ;; Visible only: skip invisible parts due to folding. + (if (and visible-only (org-invisible-p nil t)) + (progn + (goto-char (org-find-visible)) + (when (and (eolp) (not (eobp))) (forward-char))) + ;; Find current element's type and parse it accordingly to + ;; its category. + (let* ((element (org-element--current-element + end granularity mode structure)) + (type (org-element-type element)) + (cbeg (org-element-property :contents-begin element))) + (goto-char (org-element-property :end element)) + ;; Fill ELEMENT contents by side-effect. + (cond + ;; If element has no contents, don't modify it. + ((not cbeg)) + ;; Greater element: parse it between `contents-begin' and + ;; `contents-end'. Ensure GRANULARITY allows recursion, + ;; or ELEMENT is a headline, in which case going inside + ;; is mandatory, in order to get sub-level headings. + ((and (memq type org-element-greater-elements) + (or (memq granularity '(element object nil)) + (and (eq granularity 'greater-element) + (eq type 'section)) + (eq type 'headline))) + (org-element--parse-elements + cbeg (org-element-property :contents-end element) + ;; Possibly switch to a special mode. + (org-element--next-mode mode type t) + (and (memq type '(item plain-list)) + (org-element-property :structure element)) + granularity visible-only element)) + ;; ELEMENT has contents. Parse objects inside, if + ;; GRANULARITY allows it. + ((memq granularity '(object nil)) + (org-element--parse-objects + cbeg (org-element-property :contents-end element) element + (org-element-restriction type)))) + (push (org-element-put-property element :parent acc) elements) + ;; Update mode. + (setq mode (org-element--next-mode mode type nil))))) ;; Return result. (apply #'org-element-set-contents acc (nreverse elements))))) @@ -4498,15 +4494,21 @@ to an appropriate container (e.g., a paragraph)." (and (memq 'latex-fragment restriction) (org-element-latex-fragment-parser))))) (?\[ - (if (eq (aref result 1) ?\[) - (and (memq 'link restriction) - (org-element-link-parser)) - (or (and (memq 'footnote-reference restriction) - (org-element-footnote-reference-parser)) - (and (memq 'timestamp restriction) - (org-element-timestamp-parser)) - (and (memq 'statistics-cookie restriction) - (org-element-statistics-cookie-parser))))) + (pcase (aref result 1) + ((and ?\[ + (guard (memq 'link restriction))) + (org-element-link-parser)) + ((and ?f + (guard (memq 'footnote-reference restriction))) + (org-element-footnote-reference-parser)) + ((and (or ?% ?/) + (guard (memq 'statistics-cookie restriction))) + (org-element-statistics-cookie-parser)) + (_ + (or (and (memq 'timestamp restriction) + (org-element-timestamp-parser)) + (and (memq 'statistics-cookie restriction) + (org-element-statistics-cookie-parser)))))) ;; This is probably a plain link. (_ (and (memq 'link restriction) (org-element-link-parser))))))) @@ -4821,10 +4823,12 @@ indentation removed from its contents." ;; ;; A single public function is provided: `org-element-cache-reset'. ;; -;; Cache is enabled by default, but can be disabled globally with +;; Cache is disabled by default for now because it sometimes triggers +;; freezes, but it can be enabled globally with ;; `org-element-use-cache'. `org-element-cache-sync-idle-time', -;; org-element-cache-sync-duration' and `org-element-cache-sync-break' -;; can be tweaked to control caching behavior. +;; `org-element-cache-sync-duration' and +;; `org-element-cache-sync-break' can be tweaked to control caching +;; behavior. ;; ;; Internally, parsed elements are stored in an AVL tree, ;; `org-element--cache'. This tree is updated lazily: whenever @@ -4892,7 +4896,7 @@ with `org-element--cache-compare'. This cache is used in A request is a vector with the following pattern: - \[NEXT BEG END OFFSET PARENT PHASE] + [NEXT BEG END OFFSET PARENT PHASE] Processing a synchronization request consists of three phases: @@ -5450,9 +5454,11 @@ the process stopped before finding the expected result." ;; element following headline above, or first element in ;; buffer. ((not cached) - (when (org-with-limited-levels (outline-previous-heading)) - (setq mode 'planning) - (forward-line)) + (if (org-with-limited-levels (outline-previous-heading)) + (progn + (setq mode 'planning) + (forward-line)) + (setq mode 'top-comment)) (skip-chars-forward " \r\t\n") (beginning-of-line)) ;; Cache returned exact match: return it. @@ -5521,7 +5527,7 @@ the process stopped before finding the expected result." ;; after it. ((and (<= elem-end pos) (/= (point-max) elem-end)) (goto-char elem-end) - (setq mode (org-element--next-mode type nil))) + (setq mode (org-element--next-mode mode type nil))) ;; A non-greater element contains point: return it. ((not (memq type org-element-greater-elements)) (throw 'exit element)) @@ -5549,7 +5555,7 @@ the process stopped before finding the expected result." (and (= cend pos) (= (point-max) pos))))) (goto-char (or next cbeg)) (setq next nil - mode (org-element--next-mode type t) + mode (org-element--next-mode mode type t) parent element end cend)))) ;; Otherwise, return ELEMENT as it is the smallest @@ -5813,7 +5819,7 @@ element. Possible types are defined in `org-element-all-elements'. Properties depend on element or object type, but always include -`:begin', `:end', `:parent' and `:post-blank' properties. +`:begin', `:end', and `:post-blank' properties. As a special case, if point is at the very beginning of the first item in a list or sub-list, returned element will be that list |