From e32a45ed36d6000db4b39171149072d11b77af72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Delafond?= Date: Sun, 13 Jul 2014 13:35:27 +0200 Subject: Imported Upstream version 8.0.7 --- lisp/org-element.el | 2390 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 1489 insertions(+), 901 deletions(-) (limited to 'lisp/org-element.el') diff --git a/lisp/org-element.el b/lisp/org-element.el index 3d67ae7..3cf87b2 100644 --- a/lisp/org-element.el +++ b/lisp/org-element.el @@ -1,6 +1,6 @@ ;;; org-element.el --- Parser And Applications for Org syntax -;; Copyright (C) 2012 Free Software Foundation, Inc. +;; Copyright (C) 2012-2013 Free Software Foundation, Inc. ;; Author: Nicolas Goaziou ;; Keywords: outlines, hypermedia, calendar, wp @@ -30,25 +30,28 @@ ;; to at least one element. ;; ;; An element always starts and ends at the beginning of a line. With -;; a few exceptions (namely `babel-call', `clock', `headline', `item', -;; `keyword', `planning', `property-drawer' and `section' types), it -;; can also accept a fixed set of keywords as attributes. Those are -;; called "affiliated keywords" to distinguish them from other -;; keywords, which are full-fledged elements. Almost all affiliated -;; keywords are referenced in `org-element-affiliated-keywords'; the -;; others are export attributes and start with "ATTR_" prefix. +;; a few exceptions (`clock', `headline', `inlinetask', `item', +;; `planning', `node-property', `quote-section' `section' and +;; `table-row' types), it can also accept a fixed set of keywords as +;; attributes. Those are called "affiliated keywords" to distinguish +;; them from other keywords, which are full-fledged elements. Almost +;; all affiliated keywords are referenced in +;; `org-element-affiliated-keywords'; the others are export attributes +;; and start with "ATTR_" prefix. ;; ;; Element containing other elements (and only elements) are called ;; greater elements. Concerned types are: `center-block', `drawer', ;; `dynamic-block', `footnote-definition', `headline', `inlinetask', -;; `item', `plain-list', `quote-block', `section' and `special-block'. +;; `item', `plain-list', `property-drawer', `quote-block', `section' +;; and `special-block'. ;; ;; Other element types are: `babel-call', `clock', `comment', -;; `comment-block', `example-block', `export-block', `fixed-width', -;; `horizontal-rule', `keyword', `latex-environment', `paragraph', -;; `planning', `property-drawer', `quote-section', `src-block', -;; `table', `table-row' and `verse-block'. Among them, `paragraph' -;; and `verse-block' types can contain Org objects and plain text. +;; `comment-block', `diary-sexp', `example-block', `export-block', +;; `fixed-width', `horizontal-rule', `keyword', `latex-environment', +;; `node-property', `paragraph', `planning', `quote-section', +;; `src-block', `table', `table-row' and `verse-block'. Among them, +;; `paragraph' and `verse-block' types can contain Org objects and +;; plain text. ;; ;; Objects are related to document's contents. Some of them are ;; recursive. Associated types are of the following: `bold', `code', @@ -59,7 +62,7 @@ ;; `table-cell', `target', `timestamp', `underline' and `verbatim'. ;; ;; Some elements also have special properties whose value can hold -;; objects themselves (i.e. an item tag or an headline name). Such +;; objects themselves (i.e. an item tag or a headline name). Such ;; values are called "secondary strings". Any object belongs to ;; either an element or a secondary string. ;; @@ -69,9 +72,15 @@ ;; refer to the beginning and ending buffer positions of the ;; considered element or object, `:post-blank', which holds the number ;; of blank lines, or white spaces, at its end and `:parent' which -;; refers to the element or object containing it. Greater elements -;; and elements containing objects will also have `:contents-begin' -;; and `:contents-end' properties to delimit contents. +;; refers to the element or object containing it. Greater elements, +;; elements and objects containing objects will also have +;; `:contents-begin' and `:contents-end' properties to delimit +;; contents. Eventually, greater elements and elements accepting +;; affiliated keywords will have a `:post-affiliated' property, +;; referring to the buffer position after all such keywords. +;; +;; At the lowest level, a `:parent' property is also attached to any +;; string, as a text property. ;; ;; Lisp-wise, an element or an object can be represented as a list. ;; It follows the pattern (TYPE PROPERTIES CONTENTS), where: @@ -107,11 +116,10 @@ ;;; Code: -(eval-when-compile - (require 'cl)) - +(eval-when-compile (require 'cl)) (require 'org) + ;;; Definitions And Rules ;; @@ -128,6 +136,8 @@ org-outline-regexp "\\|" ;; Footnote definitions. "\\[\\(?:[0-9]+\\|fn:[-_[:word:]]+\\)\\]" "\\|" + ;; Diary sexps. + "%%(" "\\|" "[ \t]*\\(?:" ;; Empty lines. "$" "\\|" @@ -150,7 +160,7 @@ ;; Lists. (let ((term (case org-plain-list-ordered-item-terminator (?\) ")") (?. "\\.") (otherwise "[.)]"))) - (alpha (and org-alphabetical-lists "\\|[A-Za-z]"))) + (alpha (and org-list-allow-alphabetical "\\|[A-Za-z]"))) (concat "\\(?:[-+*]\\|\\(?:[0-9]+" alpha "\\)" term "\\)" "\\(?:[ \t]\\|$\\)")) "\\)\\)") @@ -160,22 +170,23 @@ is not sufficient to know if point is at a paragraph ending. See `org-element-paragraph-parser' for more information.") (defconst org-element-all-elements - '(center-block clock comment comment-block drawer dynamic-block example-block - export-block fixed-width footnote-definition headline - horizontal-rule inlinetask item keyword latex-environment - babel-call paragraph plain-list planning property-drawer - quote-block quote-section section special-block src-block table - table-row verse-block) + '(babel-call center-block clock comment comment-block diary-sexp drawer + dynamic-block example-block export-block fixed-width + footnote-definition headline horizontal-rule inlinetask item + keyword latex-environment node-property paragraph plain-list + planning property-drawer quote-block quote-section section + special-block src-block table table-row verse-block) "Complete list of element types.") (defconst org-element-greater-elements '(center-block drawer dynamic-block footnote-definition headline inlinetask - item plain-list quote-block section special-block table) + item plain-list property-drawer quote-block section + special-block table) "List of recursive element types aka Greater Elements.") (defconst org-element-all-successors '(export-snippet footnote-reference inline-babel-call inline-src-block - latex-or-entity line-break link macro radio-target + latex-or-entity line-break link macro plain-link radio-target statistics-cookie sub/superscript table-cell target text-markup timestamp) "Complete list of successors.") @@ -187,7 +198,6 @@ is not sufficient to know if point is at a paragraph ending. See (verbatim . text-markup) (entity . latex-or-entity) (latex-fragment . latex-or-entity)) "Alist of translations between object type and successor name. - Sharing the same successor comes handy when, for example, the regexp matching one object can also match the other object.") @@ -199,11 +209,11 @@ regexp matching one object can also match the other object.") "Complete list of object types.") (defconst org-element-recursive-objects - '(bold italic link macro subscript radio-target strike-through superscript + '(bold italic link subscript radio-target strike-through superscript table-cell underline) "List of recursive object types.") -(defconst org-element-block-name-alist +(defvar org-element-block-name-alist '(("CENTER" . org-element-center-block-parser) ("COMMENT" . org-element-comment-block-parser) ("EXAMPLE" . org-element-example-block-parser) @@ -214,6 +224,12 @@ regexp matching one object can also match the other object.") Names must be uppercase. Any block whose name has no association is parsed with `org-element-special-block-parser'.") +(defconst org-element-link-type-is-file + '("file" "file+emacs" "file+sys" "docview") + "List of link types equivalent to \"file\". +Only these types can accept search options and an explicit +application to open them.") + (defconst org-element-affiliated-keywords '("CAPTION" "DATA" "HEADER" "HEADERS" "LABEL" "NAME" "PLOT" "RESNAME" "RESULT" "RESULTS" "SOURCE" "SRCNAME" "TBLNAME") @@ -242,8 +258,8 @@ Don't modify it, set `org-element-affiliated-keywords' instead.") The key is the old name and the value the new one. The property holding their value will be named after the translated name.") -(defconst org-element-multiple-keywords '("HEADER") - "List of affiliated keywords that can occur more that once in an element. +(defconst org-element-multiple-keywords '("CAPTION" "HEADER") + "List of affiliated keywords that can occur more than once in an element. Their value will be consed into a list of strings, which will be returned as the value of the property. @@ -254,8 +270,8 @@ This list is checked after translations have been applied. See By default, all keywords setting attributes (i.e. \"ATTR_LATEX\") allow multiple occurrences and need not to be in this list.") -(defconst org-element-parsed-keywords '("AUTHOR" "CAPTION" "DATE" "TITLE") - "List of keywords whose value can be parsed. +(defconst org-element-parsed-keywords '("CAPTION") + "List of affiliated keywords whose value can be parsed. Their value will be stored as a secondary string: a list of strings and objects. @@ -264,10 +280,10 @@ This list is checked after translations have been applied. See `org-element-keyword-translation-alist'.") (defconst org-element-dual-keywords '("CAPTION" "RESULTS") - "List of keywords which can have a secondary value. + "List of affiliated keywords which can have a secondary value. In Org syntax, they can be written with optional square brackets -before the colons. For example, results keyword can be +before the colons. For example, RESULTS keyword can be associated to a hash value with the following: #+RESULTS[hash-string]: some-source @@ -275,46 +291,40 @@ associated to a hash value with the following: This list is checked after translations have been applied. See `org-element-keyword-translation-alist'.") +(defconst org-element-document-properties '("AUTHOR" "DATE" "TITLE") + "List of properties associated to the whole document. +Any keyword in this list will have its value parsed and stored as +a secondary string.") + (defconst org-element-object-restrictions - '((bold export-snippet inline-babel-call inline-src-block latex-or-entity link - radio-target sub/superscript target text-markup timestamp) - (footnote-reference export-snippet footnote-reference inline-babel-call - inline-src-block latex-or-entity line-break link macro - radio-target sub/superscript target text-markup - timestamp) - (headline inline-babel-call inline-src-block latex-or-entity link macro - radio-target statistics-cookie sub/superscript target text-markup - timestamp) - (inlinetask inline-babel-call inline-src-block latex-or-entity link macro - radio-target sub/superscript target text-markup timestamp) - (italic export-snippet inline-babel-call inline-src-block latex-or-entity - link radio-target sub/superscript target text-markup timestamp) - (item export-snippet footnote-reference inline-babel-call latex-or-entity - link macro radio-target sub/superscript target text-markup) - (keyword latex-or-entity macro sub/superscript text-markup) - (link export-snippet inline-babel-call inline-src-block latex-or-entity link - sub/superscript text-markup) - (macro macro) - (paragraph export-snippet footnote-reference inline-babel-call - inline-src-block latex-or-entity line-break link macro - radio-target statistics-cookie sub/superscript target text-markup - timestamp) - (radio-target export-snippet latex-or-entity sub/superscript) - (strike-through export-snippet inline-babel-call inline-src-block - latex-or-entity link radio-target sub/superscript target - text-markup timestamp) - (subscript export-snippet inline-babel-call inline-src-block latex-or-entity - sub/superscript target text-markup) - (superscript export-snippet inline-babel-call inline-src-block - latex-or-entity sub/superscript target text-markup) - (table-cell export-snippet latex-or-entity link macro radio-target - sub/superscript target text-markup timestamp) - (table-row table-cell) - (underline export-snippet inline-babel-call inline-src-block latex-or-entity - link radio-target sub/superscript target text-markup timestamp) - (verse-block footnote-reference inline-babel-call inline-src-block - latex-or-entity line-break link macro radio-target - sub/superscript target text-markup timestamp)) + (let* ((standard-set + (remq 'plain-link (remq 'table-cell org-element-all-successors))) + (standard-set-no-line-break (remq 'line-break standard-set))) + `((bold ,@standard-set) + (footnote-reference ,@standard-set) + (headline ,@standard-set-no-line-break) + (inlinetask ,@standard-set-no-line-break) + (italic ,@standard-set) + (item ,@standard-set-no-line-break) + (keyword ,@standard-set) + ;; Ignore all links excepted plain links in a link description. + ;; Also ignore radio-targets and line breaks. + (link export-snippet inline-babel-call inline-src-block latex-or-entity + macro plain-link statistics-cookie sub/superscript text-markup) + (paragraph ,@standard-set) + ;; Remove any variable object from radio target as it would + ;; prevent it from being properly recognized. + (radio-target latex-or-entity sub/superscript) + (strike-through ,@standard-set) + (subscript ,@standard-set) + (superscript ,@standard-set) + ;; Ignore inline babel call and inline src block as formulas are + ;; possible. Also ignore line breaks and statistics cookies. + (table-cell export-snippet footnote-reference latex-or-entity link macro + radio-target sub/superscript target text-markup timestamp) + (table-row table-cell) + (underline ,@standard-set) + (verse-block ,@standard-set))) "Alist of objects restrictions. CAR is an element or object type containing objects and CDR is @@ -322,8 +332,7 @@ a list of successors that will be called within an element or object of such type. For example, in a `radio-target' object, one can only find -entities, export snippets, latex-fragments, subscript and -superscript. +entities, latex-fragments, subscript and superscript. This alist also applies to secondary string. For example, an `headline' type element doesn't directly contain objects, but @@ -336,6 +345,11 @@ still has an entry since one of its properties (`:title') does.") (footnote-reference . :inline-definition)) "Alist between element types and location of secondary value.") +(defconst org-element-object-variables '(org-link-abbrev-alist-local) + "List of buffer-local variables used when parsing objects. +These variables are copied to the temporary buffer created by +`org-export-secondary-string'.") + ;;; Accessors and Setters @@ -363,11 +377,14 @@ It can also return the following special value: (defsubst org-element-property (property element) "Extract the value from the PROPERTY of an ELEMENT." - (plist-get (nth 1 element) property)) + (if (stringp element) (get-text-property 0 property element) + (plist-get (nth 1 element) property))) (defsubst org-element-contents (element) "Extract contents from an ELEMENT." - (and (consp element) (nthcdr 2 element))) + (cond ((not (consp element)) nil) + ((symbolp (car element)) (nthcdr 2 element)) + (t element))) (defsubst org-element-restriction (element) "Return restriction associated to ELEMENT. @@ -379,14 +396,15 @@ element or object type." (defsubst org-element-put-property (element property value) "In ELEMENT set PROPERTY to VALUE. Return modified element." - (when (consp element) - (setcar (cdr element) (plist-put (nth 1 element) property value))) - element) + (if (stringp element) (org-add-props element nil property value) + (setcar (cdr element) (plist-put (nth 1 element) property value)) + element)) (defsubst org-element-set-contents (element &rest contents) "Set ELEMENT contents to CONTENTS. Return modified element." (cond ((not element) (list contents)) + ((not (symbolp (car element))) contents) ((cdr element) (setcdr (cdr element) contents)) (t (nconc element contents)))) @@ -415,18 +433,18 @@ objects, or a strings. The function takes care of setting `:parent' property for CHILD. Return parent element." - (if (not parent) children - ;; Link every child to PARENT. - (mapc (lambda (child) - (unless (stringp child) - (org-element-put-property child :parent parent))) - children) - ;; Add CHILDREN at the end of PARENT contents. + ;; Link every child to PARENT. If PARENT is nil, it is a secondary + ;; string: parent is the list itself. + (mapc (lambda (child) + (org-element-put-property child :parent (or parent children))) + children) + ;; Add CHILDREN at the end of PARENT contents. + (when parent (apply 'org-element-set-contents parent - (nconc (org-element-contents parent) children)) - ;; Return modified PARENT element. - parent)) + (nconc (org-element-contents parent) children))) + ;; Return modified PARENT element. + (or parent children)) @@ -466,24 +484,27 @@ Return parent element." ;;;; Center Block -(defun org-element-center-block-parser (limit) +(defun org-element-center-block-parser (limit affiliated) "Parse a center block. -LIMIT bounds the search. +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. Return a list whose CAR is `center-block' and CDR is a plist containing `:begin', `:end', `:hiddenp', `:contents-begin', -`:contents-end' and `:post-blank' keywords. +`:contents-end', `:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the block." (let ((case-fold-search t)) (if (not (save-excursion - (re-search-forward "^[ \t]*#\\+END_CENTER" limit t))) + (re-search-forward "^[ \t]*#\\+END_CENTER[ \t]*$" limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((block-end-line (match-beginning 0))) - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) ;; Empty blocks have no contents. (contents-begin (progn (forward-line) (and (< (point) block-end-line) @@ -494,7 +515,8 @@ Assume point is at the beginning of the block." (forward-line) (point))) (end (save-excursion (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'center-block (nconc (list :begin begin @@ -502,8 +524,9 @@ Assume point is at the beginning of the block." :hiddenp hidden :contents-begin contents-begin :contents-end contents-end - :post-blank (count-lines pos-before-blank end)) - (cadr keywords)))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated)))))))) (defun org-element-center-block-interpreter (center-block contents) "Interpret CENTER-BLOCK element as Org syntax. @@ -513,48 +536,52 @@ CONTENTS is the contents of the element." ;;;; Drawer -(defun org-element-drawer-parser (limit) +(defun org-element-drawer-parser (limit affiliated) "Parse a drawer. -LIMIT bounds the search. +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. Return a list whose CAR is `drawer' and CDR is a plist containing `:drawer-name', `:begin', `:end', `:hiddenp', `:contents-begin', -`:contents-end' and `:post-blank' keywords. +`:contents-end', `:post-blank' and `:post-affiliated' keywords. Assume point is at beginning of drawer." (let ((case-fold-search t)) - (if (not (save-excursion (re-search-forward "^[ \t]*:END:" limit t))) + (if (not (save-excursion (re-search-forward "^[ \t]*:END:[ \t]*$" limit t))) ;; Incomplete drawer: parse it as a paragraph. - (org-element-paragraph-parser limit) - (let ((drawer-end-line (match-beginning 0))) - (save-excursion - (let* ((case-fold-search t) - (name (progn (looking-at org-drawer-regexp) - (org-match-string-no-properties 1))) - (keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) - ;; Empty drawers have no contents. - (contents-begin (progn (forward-line) - (and (< (point) drawer-end-line) - (point)))) - (contents-end (and contents-begin drawer-end-line)) - (hidden (org-invisible-p2)) - (pos-before-blank (progn (goto-char drawer-end-line) - (forward-line) - (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) - (list 'drawer - (nconc - (list :begin begin - :end end - :drawer-name name - :hiddenp hidden - :contents-begin contents-begin - :contents-end contents-end - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + (org-element-paragraph-parser limit affiliated) + (save-excursion + (let* ((drawer-end-line (match-beginning 0)) + (name (progn (looking-at org-drawer-regexp) + (org-match-string-no-properties 1))) + (begin (car affiliated)) + (post-affiliated (point)) + ;; Empty drawers have no contents. + (contents-begin (progn (forward-line) + (and (< (point) drawer-end-line) + (point)))) + (contents-end (and contents-begin drawer-end-line)) + (hidden (org-invisible-p2)) + (pos-before-blank (progn (goto-char drawer-end-line) + (forward-line) + (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) + (list 'drawer + (nconc + (list :begin begin + :end end + :drawer-name name + :hiddenp hidden + :contents-begin contents-begin + :contents-end contents-end + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated)))))))) (defun org-element-drawer-interpreter (drawer contents) "Interpret DRAWER element as Org syntax. @@ -566,28 +593,32 @@ CONTENTS is the contents of the element." ;;;; Dynamic Block -(defun org-element-dynamic-block-parser (limit) +(defun org-element-dynamic-block-parser (limit affiliated) "Parse a dynamic block. -LIMIT bounds the search. +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. Return a list whose CAR is `dynamic-block' and CDR is a plist containing `:block-name', `:begin', `:end', `:hiddenp', -`:contents-begin', `:contents-end', `:arguments' and -`:post-blank' keywords. +`:contents-begin', `:contents-end', `:arguments', `:post-blank' +and `:post-affiliated' keywords. Assume point is at beginning of dynamic block." (let ((case-fold-search t)) - (if (not (save-excursion (re-search-forward org-dblock-end-re limit t))) + (if (not (save-excursion + (re-search-forward "^[ \t]*#\\+END:?[ \t]*$" limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((block-end-line (match-beginning 0))) (save-excursion (let* ((name (progn (looking-at org-dblock-start-re) (org-match-string-no-properties 1))) (arguments (org-match-string-no-properties 3)) - (keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (begin (car affiliated)) + (post-affiliated (point)) ;; Empty blocks have no contents. (contents-begin (progn (forward-line) (and (< (point) block-end-line) @@ -598,7 +629,8 @@ Assume point is at beginning of dynamic block." (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'dynamic-block (nconc (list :begin begin @@ -608,8 +640,9 @@ Assume point is at beginning of dynamic block." :hiddenp hidden :contents-begin contents-begin :contents-end contents-end - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-dynamic-block-interpreter (dynamic-block contents) "Interpret DYNAMIC-BLOCK element as Org syntax. @@ -623,28 +656,31 @@ CONTENTS is the contents of the element." ;;;; Footnote Definition -(defun org-element-footnote-definition-parser (limit) +(defun org-element-footnote-definition-parser (limit affiliated) "Parse a footnote definition. -LIMIT bounds the search. +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. Return a list whose CAR is `footnote-definition' and CDR is a plist containing `:label', `:begin' `:end', `:contents-begin', -`:contents-end' and `:post-blank' keywords. +`:contents-end', `:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the footnote definition." (save-excursion (let* ((label (progn (looking-at org-footnote-definition-re) (org-match-string-no-properties 1))) - (keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (begin (car affiliated)) + (post-affiliated (point)) (ending (save-excursion (if (progn (end-of-line) (re-search-forward (concat org-outline-regexp-bol "\\|" org-footnote-definition-re "\\|" - "^[ \t]*$") limit 'move)) + "^\\([ \t]*\n\\)\\{2,\\}") limit 'move)) (match-beginning 0) (point)))) (contents-begin (progn (search-forward "]") @@ -653,7 +689,8 @@ Assume point is at the beginning of the footnote definition." (contents-end (and contents-begin ending)) (end (progn (goto-char ending) (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'footnote-definition (nconc (list :label label @@ -661,8 +698,9 @@ Assume point is at the beginning of the footnote definition." :end end :contents-begin contents-begin :contents-end contents-end - :post-blank (count-lines ending end)) - (cadr keywords)))))) + :post-blank (count-lines ending end) + :post-affiliated post-affiliated) + (cdr affiliated)))))) (defun org-element-footnote-definition-interpreter (footnote-definition contents) "Interpret FOOTNOTE-DEFINITION element as Org syntax. @@ -675,19 +713,19 @@ CONTENTS is the contents of the footnote-definition." ;;;; Headline (defun org-element-headline-parser (limit &optional raw-secondary-p) - "Parse an headline. + "Parse a headline. Return a list whose CAR is `headline' and CDR is a plist -containing `:raw-value', `:title', `:begin', `:end', -`:pre-blank', `:hiddenp', `:contents-begin' and `:contents-end', -`:level', `:priority', `:tags', `:todo-keyword',`:todo-type', -`:scheduled', `:deadline', `:timestamp', `:clock', `:category', -`:quotedp', `:archivedp', `:commentedp' and `:footnote-section-p' -keywords. +containing `:raw-value', `:title', `:alt-title', `:begin', +`:end', `:pre-blank', `:hiddenp', `:contents-begin' and +`:contents-end', `:level', `:priority', `:tags', +`:todo-keyword',`:todo-type', `:scheduled', `:deadline', +`:closed', `:quotedp', `:archivedp', `:commentedp' and +`:footnote-section-p' keywords. The plist also contains any property set in the property drawer, -with its name in lowercase, the underscores replaced with hyphens -and colons at the beginning (i.e. `:custom-id'). +with its name in upper cases and colons added at the +beginning (i.e. `:CUSTOM_ID'). When RAW-SECONDARY-P is non-nil, headline's title will not be parsed as a secondary string, but as a plain string instead. @@ -713,25 +751,37 @@ Assume point is at beginning of the headline." (archivedp (member org-archive-tag tags)) (footnote-section-p (and org-footnote-section (string= org-footnote-section raw-value))) - ;; Normalize property names: ":SOME_PROP:" becomes - ;; ":some-prop". - (standard-props (let (plist) - (mapc - (lambda (p) - (let ((p-name (downcase (car p)))) - (while (string-match "_" p-name) - (setq p-name - (replace-match "-" nil nil p-name))) - (setq p-name (intern (concat ":" p-name))) - (setq plist - (plist-put plist p-name (cdr p))))) - (org-entry-properties nil 'standard)) - plist)) - (time-props (org-entry-properties nil 'special "CLOCK")) - (scheduled (cdr (assoc "SCHEDULED" time-props))) - (deadline (cdr (assoc "DEADLINE" time-props))) - (clock (cdr (assoc "CLOCK" time-props))) - (timestamp (cdr (assoc "TIMESTAMP" time-props))) + ;; Upcase property names. It avoids confusion between + ;; properties obtained through property drawer and default + ;; properties from the parser (e.g. `:end' and :END:) + (standard-props + (let (plist) + (mapc + (lambda (p) + (setq plist + (plist-put plist + (intern (concat ":" (upcase (car p)))) + (cdr p)))) + (org-entry-properties nil 'standard)) + plist)) + (time-props + ;; Read time properties on the line below the headline. + (save-excursion + (when (progn (forward-line) + (looking-at org-planning-or-clock-line-re)) + (let ((end (line-end-position)) plist) + (while (re-search-forward + org-keyword-time-not-clock-regexp end t) + (goto-char (match-end 1)) + (skip-chars-forward " \t") + (let ((keyword (match-string 1)) + (time (org-element-timestamp-parser))) + (cond ((equal keyword org-scheduled-string) + (setq plist (plist-put plist :scheduled time))) + ((equal keyword org-deadline-string) + (setq plist (plist-put plist :deadline time))) + (t (setq plist (plist-put plist :closed time)))))) + plist)))) (begin (point)) (end (save-excursion (goto-char (org-end-of-subtree t t)))) (pos-after-head (progn (forward-line) (point))) @@ -773,10 +823,6 @@ Assume point is at beginning of the headline." :tags tags :todo-keyword todo :todo-type todo-type - :scheduled scheduled - :deadline deadline - :timestamp timestamp - :clock clock :post-blank (count-lines (if (not contents-end) pos-after-head (goto-char contents-end) @@ -787,7 +833,15 @@ Assume point is at beginning of the headline." :archivedp archivedp :commentedp commentedp :quotedp quotedp) + time-props standard-props)))) + (let ((alt-title (org-element-property :ALT_TITLE headline))) + (when alt-title + (org-element-put-property + headline :alt-title + (if raw-secondary-p alt-title + (org-element-parse-secondary-string + alt-title (org-element-restriction 'headline) headline))))) (org-element-put-property headline :title (if raw-secondary-p raw-value @@ -850,12 +904,11 @@ Return a list whose CAR is `inlinetask' and CDR is a plist containing `:title', `:begin', `:end', `:hiddenp', `:contents-begin' and `:contents-end', `:level', `:priority', `:raw-value', `:tags', `:todo-keyword', `:todo-type', -`:scheduled', `:deadline', `:timestamp', `:clock' and -`:post-blank' keywords. +`:scheduled', `:deadline', `:closed' and `:post-blank' keywords. The plist also contains any property set in the property drawer, -with its name in lowercase, the underscores replaced with hyphens -and colons at the beginning (i.e. `:custom-id'). +with its name in upper cases and colons added at the +beginning (i.e. `:CUSTOM_ID'). When optional argument RAW-SECONDARY-P is non-nil, inline-task's title will not be parsed as a secondary string, but as a plain @@ -863,8 +916,7 @@ string instead. Assume point is at beginning of the inline task." (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (point)) (components (org-heading-components)) (todo (nth 2 components)) (todo-type (and todo @@ -872,25 +924,38 @@ Assume point is at beginning of the inline task." (tags (let ((raw-tags (nth 5 components))) (and raw-tags (org-split-string raw-tags ":")))) (raw-value (or (nth 4 components) "")) - ;; Normalize property names: ":SOME_PROP:" becomes - ;; ":some-prop". - (standard-props (let (plist) - (mapc - (lambda (p) - (let ((p-name (downcase (car p)))) - (while (string-match "_" p-name) - (setq p-name - (replace-match "-" nil nil p-name))) - (setq p-name (intern (concat ":" p-name))) - (setq plist - (plist-put plist p-name (cdr p))))) - (org-entry-properties nil 'standard)) - plist)) - (time-props (org-entry-properties nil 'special "CLOCK")) - (scheduled (cdr (assoc "SCHEDULED" time-props))) - (deadline (cdr (assoc "DEADLINE" time-props))) - (clock (cdr (assoc "CLOCK" time-props))) - (timestamp (cdr (assoc "TIMESTAMP" time-props))) + ;; Upcase property names. It avoids confusion between + ;; properties obtained through property drawer and default + ;; properties from the parser (e.g. `:end' and :END:) + (standard-props + (let (plist) + (mapc + (lambda (p) + (setq plist + (plist-put plist + (intern (concat ":" (upcase (car p)))) + (cdr p)))) + (org-entry-properties nil 'standard)) + plist)) + (time-props + ;; Read time properties on the line below the inlinetask + ;; opening string. + (save-excursion + (when (progn (forward-line) + (looking-at org-planning-or-clock-line-re)) + (let ((end (line-end-position)) plist) + (while (re-search-forward + org-keyword-time-not-clock-regexp end t) + (goto-char (match-end 1)) + (skip-chars-forward " \t") + (let ((keyword (match-string 1)) + (time (org-element-timestamp-parser))) + (cond ((equal keyword org-scheduled-string) + (setq plist (plist-put plist :scheduled time))) + ((equal keyword org-deadline-string) + (setq plist (plist-put plist :deadline time))) + (t (setq plist (plist-put plist :closed time)))))) + plist)))) (task-end (save-excursion (end-of-line) (and (re-search-forward "^\\*+ END" limit t) @@ -904,7 +969,8 @@ Assume point is at beginning of the inline task." (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol)))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position)))) (inlinetask (list 'inlinetask (nconc @@ -919,13 +985,9 @@ Assume point is at beginning of the inline task." :tags tags :todo-keyword todo :todo-type todo-type - :scheduled scheduled - :deadline deadline - :timestamp timestamp - :clock clock :post-blank (count-lines before-blank end)) - standard-props - (cadr keywords))))) + time-props + standard-props)))) (org-element-put-property inlinetask :title (if raw-secondary-p raw-value @@ -1057,7 +1119,11 @@ Assume point is at the beginning of the item." (defun org-element-item-interpreter (item contents) "Interpret ITEM element as Org syntax. CONTENTS is the contents of the element." - (let* ((bullet (org-list-bullet-string (org-element-property :bullet item))) + (let* ((bullet (let ((bullet (org-element-property :bullet item))) + (org-list-bullet-string + (cond ((not (string-match "[0-9a-zA-Z]" bullet)) "- ") + ((eq org-plain-list-ordered-item-terminator ?\)) "1)") + (t "1."))))) (checkbox (org-element-property :checkbox item)) (counter (org-element-property :counter item)) (tag (let ((tag (org-element-property :tag item))) @@ -1076,23 +1142,28 @@ CONTENTS is the contents of the element." (off "[ ] ") (trans "[-] ")) (and tag (format "%s :: " tag)) - (let ((contents (replace-regexp-in-string - "\\(^\\)[ \t]*\\S-" ind contents nil nil 1))) - (if item-starts-with-par-p (org-trim contents) - (concat "\n" contents)))))) + (when contents + (let ((contents (replace-regexp-in-string + "\\(^\\)[ \t]*\\S-" ind contents nil nil 1))) + (if item-starts-with-par-p (org-trim contents) + (concat "\n" contents))))))) ;;;; Plain List -(defun org-element-plain-list-parser (limit &optional structure) +(defun org-element-plain-list-parser (limit affiliated structure) "Parse a plain list. -Optional argument STRUCTURE, when non-nil, is the structure of -the plain list being parsed. +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. STRUCTURE is the structure of the plain list being +parsed. Return a list whose CAR is `plain-list' and CDR is a plist containing `:type', `:begin', `:end', `:contents-begin' and -`:contents-end', `:structure' and `:post-blank' keywords. +`:contents-end', `:structure', `:post-blank' and +`:post-affiliated' keywords. Assume point is at the beginning of the list." (save-excursion @@ -1101,14 +1172,13 @@ Assume point is at the beginning of the list." (parents (org-list-parents-alist struct)) (type (org-list-get-list-type (point) struct prevs)) (contents-begin (point)) - (keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (begin (car affiliated)) (contents-end (progn (goto-char (org-list-get-list-end (point) struct prevs)) (unless (bolp) (forward-line)) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (if (= (point) limit) limit (line-beginning-position))))) ;; Return value. (list 'plain-list (nconc @@ -1118,8 +1188,9 @@ Assume point is at the beginning of the list." :contents-begin contents-begin :contents-end contents-end :structure struct - :post-blank (count-lines contents-end end)) - (cadr keywords)))))) + :post-blank (count-lines contents-end end) + :post-affiliated contents-begin) + (cdr affiliated)))))) (defun org-element-plain-list-interpreter (plain-list contents) "Interpret PLAIN-LIST element as Org syntax. @@ -1131,27 +1202,83 @@ CONTENTS is the contents of the element." (buffer-string))) +;;;; Property Drawer + +(defun org-element-property-drawer-parser (limit affiliated) + "Parse a property drawer. + +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. + +Return a list whose CAR is `property-drawer' and CDR is a plist +containing `:begin', `:end', `:hiddenp', `:contents-begin', +`:contents-end', `:post-blank' and `:post-affiliated' keywords. + +Assume point is at the beginning of the property drawer." + (save-excursion + (let ((case-fold-search t)) + (if (not (save-excursion + (re-search-forward "^[ \t]*:END:[ \t]*$" limit t))) + ;; Incomplete drawer: parse it as a paragraph. + (org-element-paragraph-parser limit affiliated) + (save-excursion + (let* ((drawer-end-line (match-beginning 0)) + (begin (car affiliated)) + (post-affiliated (point)) + (contents-begin (progn (forward-line) + (and (< (point) drawer-end-line) + (point)))) + (contents-end (and contents-begin drawer-end-line)) + (hidden (org-invisible-p2)) + (pos-before-blank (progn (goto-char drawer-end-line) + (forward-line) + (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) + (list 'property-drawer + (nconc + (list :begin begin + :end end + :hiddenp hidden + :contents-begin contents-begin + :contents-end contents-end + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) + +(defun org-element-property-drawer-interpreter (property-drawer contents) + "Interpret PROPERTY-DRAWER element as Org syntax. +CONTENTS is the properties within the drawer." + (format ":PROPERTIES:\n%s:END:" contents)) + + ;;;; Quote Block -(defun org-element-quote-block-parser (limit) +(defun org-element-quote-block-parser (limit affiliated) "Parse a quote block. -LIMIT bounds the search. +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. Return a list whose CAR is `quote-block' and CDR is a plist containing `:begin', `:end', `:hiddenp', `:contents-begin', -`:contents-end' and `:post-blank' keywords. +`:contents-end', `:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the block." (let ((case-fold-search t)) (if (not (save-excursion - (re-search-forward "^[ \t]*#\\+END_QUOTE" limit t))) + (re-search-forward "^[ \t]*#\\+END_QUOTE[ \t]*$" limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((block-end-line (match-beginning 0))) (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) ;; Empty blocks have no contents. (contents-begin (progn (forward-line) (and (< (point) block-end-line) @@ -1162,7 +1289,8 @@ Assume point is at the beginning of the block." (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'quote-block (nconc (list :begin begin @@ -1170,8 +1298,9 @@ Assume point is at the beginning of the block." :hiddenp hidden :contents-begin contents-begin :contents-end contents-end - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-quote-block-interpreter (quote-block contents) "Interpret QUOTE-BLOCK element as Org syntax. @@ -1213,27 +1342,33 @@ CONTENTS is the contents of the element." ;;;; Special Block -(defun org-element-special-block-parser (limit) +(defun org-element-special-block-parser (limit affiliated) "Parse a special block. -LIMIT bounds the search. +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. Return a list whose CAR is `special-block' and CDR is a plist containing `:type', `:begin', `:end', `:hiddenp', -`:contents-begin', `:contents-end' and `:post-blank' keywords. +`:contents-begin', `:contents-end', `:post-blank' and +`:post-affiliated' keywords. Assume point is at the beginning of the block." (let* ((case-fold-search t) - (type (progn (looking-at "[ \t]*#\\+BEGIN_\\(S-+\\)") + (type (progn (looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") (upcase (match-string-no-properties 1))))) (if (not (save-excursion - (re-search-forward (concat "^[ \t]*#\\+END_" type) limit t))) + (re-search-forward + (format "^[ \t]*#\\+END_%s[ \t]*$" (regexp-quote type)) + limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((block-end-line (match-beginning 0))) (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) ;; Empty blocks have no contents. (contents-begin (progn (forward-line) (and (< (point) block-end-line) @@ -1243,8 +1378,9 @@ Assume point is at the beginning of the block." (pos-before-blank (progn (goto-char block-end-line) (forward-line) (point))) - (end (progn (org-skip-whitespace) - (if (eobp) (point) (point-at-bol))))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'special-block (nconc (list :type type @@ -1253,8 +1389,9 @@ Assume point is at the beginning of the block." :hiddenp hidden :contents-begin contents-begin :contents-end contents-end - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-special-block-interpreter (special-block contents) "Interpret SPECIAL-BLOCK element as Org syntax. @@ -1280,27 +1417,35 @@ CONTENTS is the contents of the element." ;;;; Babel Call -(defun org-element-babel-call-parser (limit) +(defun org-element-babel-call-parser (limit affiliated) "Parse a babel call. -LIMIT bounds the search. +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. Return a list whose CAR is `babel-call' and CDR is a plist -containing `:begin', `:end', `:info' and `:post-blank' as -keywords." +containing `:begin', `:end', `:info', `:post-blank' and +`:post-affiliated' as keywords." (save-excursion (let ((case-fold-search t) (info (progn (looking-at org-babel-block-lob-one-liner-regexp) (org-babel-lob-get-info))) - (begin (point-at-bol)) + (begin (car affiliated)) + (post-affiliated (point)) (pos-before-blank (progn (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'babel-call - (list :begin begin - :end end - :info info - :post-blank (count-lines pos-before-blank end)))))) + (nconc + (list :begin begin + :end end + :info info + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated)))))) (defun org-element-babel-call-interpreter (babel-call contents) "Interpret BABEL-CALL element as Org syntax. @@ -1329,22 +1474,23 @@ as keywords." (let* ((case-fold-search nil) (begin (point)) (value (progn (search-forward org-clock-string (line-end-position) t) - (org-skip-whitespace) - (looking-at "\\[.*\\]") - (org-match-string-no-properties 0))) - (time (and (progn (goto-char (match-end 0)) - (looking-at " +=> +\\(\\S-+\\)[ \t]*$")) - (org-match-string-no-properties 1))) - (status (if time 'closed 'running)) + (skip-chars-forward " \t") + (org-element-timestamp-parser))) + (duration (and (search-forward " => " (line-end-position) t) + (progn (skip-chars-forward " \t") + (looking-at "\\(\\S-+\\)[ \t]*$")) + (org-match-string-no-properties 1))) + (status (if duration 'closed 'running)) (post-blank (let ((before-blank (progn (forward-line) (point)))) (skip-chars-forward " \r\t\n" limit) - (unless (eobp) (beginning-of-line)) + (skip-chars-backward " \t") + (unless (bolp) (end-of-line)) (count-lines before-blank (point)))) (end (point))) (list 'clock (list :status status :value value - :time time + :duration duration :begin begin :end end :post-blank post-blank))))) @@ -1353,30 +1499,34 @@ as keywords." "Interpret CLOCK element as Org syntax. CONTENTS is nil." (concat org-clock-string " " - (org-element-property :value clock) - (let ((time (org-element-property :time clock))) - (and time + (org-element-timestamp-interpreter + (org-element-property :value clock) nil) + (let ((duration (org-element-property :duration clock))) + (and duration (concat " => " (apply 'format "%2s:%02s" - (org-split-string time ":"))))))) + (org-split-string duration ":"))))))) ;;;; Comment -(defun org-element-comment-parser (limit) +(defun org-element-comment-parser (limit affiliated) "Parse a comment. -LIMIT bounds the search. +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. Return a list whose CAR is `comment' and CDR is a plist -containing `:begin', `:end', `:value' and `:post-blank' -keywords. +containing `:begin', `:end', `:value', `:post-blank', +`:post-affiliated' keywords. Assume point is at comment beginning." (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) (value (prog2 (looking-at "[ \t]*# ?") (buffer-substring-no-properties (match-end 0) (line-end-position)) @@ -1396,14 +1546,16 @@ Assume point is at comment beginning." (point))) (end (progn (goto-char com-end) (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'comment (nconc (list :begin begin :end end :value value - :post-blank (count-lines com-end end)) - (cadr keywords)))))) + :post-blank (count-lines com-end end) + :post-affiliated post-affiliated) + (cdr affiliated)))))) (defun org-element-comment-interpreter (comment contents) "Interpret COMMENT element as Org syntax. @@ -1413,32 +1565,36 @@ CONTENTS is nil." ;;;; Comment Block -(defun org-element-comment-block-parser (limit) +(defun org-element-comment-block-parser (limit affiliated) "Parse an export block. -LIMIT bounds the search. +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. Return a list whose CAR is `comment-block' and CDR is a plist -containing `:begin', `:end', `:hiddenp', `:value' and -`:post-blank' keywords. +containing `:begin', `:end', `:hiddenp', `:value', `:post-blank' +and `:post-affiliated' keywords. Assume point is at comment block beginning." (let ((case-fold-search t)) (if (not (save-excursion - (re-search-forward "^[ \t]*#\\+END_COMMENT" limit t))) + (re-search-forward "^[ \t]*#\\+END_COMMENT[ \t]*$" limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((contents-end (match-beginning 0))) (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) (contents-begin (progn (forward-line) (point))) (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol)))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position)))) (value (buffer-substring-no-properties contents-begin contents-end))) (list 'comment-block @@ -1447,8 +1603,9 @@ Assume point is at comment block beginning." :end end :value value :hiddenp hidden - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-comment-block-interpreter (comment-block contents) "Interpret COMMENT-BLOCK element as Org syntax. @@ -1457,32 +1614,106 @@ CONTENTS is nil." (org-remove-indentation (org-element-property :value comment-block)))) +;;;; Diary Sexp + +(defun org-element-diary-sexp-parser (limit affiliated) + "Parse a diary sexp. + +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. + +Return a list whose CAR is `diary-sexp' and CDR is a plist +containing `:begin', `:end', `:value', `:post-blank' and +`:post-affiliated' keywords." + (save-excursion + (let ((begin (car affiliated)) + (post-affiliated (point)) + (value (progn (looking-at "\\(%%(.*\\)[ \t]*$") + (org-match-string-no-properties 1))) + (pos-before-blank (progn (forward-line) (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) + (list 'diary-sexp + (nconc + (list :value value + :begin begin + :end end + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated)))))) + +(defun org-element-diary-sexp-interpreter (diary-sexp contents) + "Interpret DIARY-SEXP as Org syntax. +CONTENTS is nil." + (org-element-property :value diary-sexp)) + + ;;;; Example Block -(defun org-element-example-block-parser (limit) +(defun org-element--remove-indentation (s &optional n) + "Remove maximum common indentation in string S and return it. +When optional argument N is a positive integer, remove exactly +that much characters from indentation, if possible, or return +S as-is otherwise. Unlike to `org-remove-indentation', this +function doesn't call `untabify' on S." + (catch 'exit + (with-temp-buffer + (insert s) + (goto-char (point-min)) + ;; Find maximum common indentation, if not specified. + (setq n (or n + (let ((min-ind (point-max))) + (save-excursion + (while (re-search-forward "^[ \t]*\\S-" nil t) + (let ((ind (1- (current-column)))) + (if (zerop ind) (throw 'exit s) + (setq min-ind (min min-ind ind)))))) + min-ind))) + (if (zerop n) s + ;; Remove exactly N indentation, but give up if not possible. + (while (not (eobp)) + (let ((ind (progn (skip-chars-forward " \t") (current-column)))) + (cond ((eolp) (delete-region (line-beginning-position) (point))) + ((< ind n) (throw 'exit s)) + (t (org-indent-line-to (- ind n)))) + (forward-line))) + (buffer-string))))) + +(defun org-element-example-block-parser (limit affiliated) "Parse an example block. -LIMIT bounds the search. +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. Return a list whose CAR is `example-block' and CDR is a plist containing `:begin', `:end', `:number-lines', `:preserve-indent', `:retain-labels', `:use-labels', `:label-fmt', `:hiddenp', -`:switches', `:value' and `:post-blank' keywords." +`:switches', `:value', `:post-blank' and `:post-affiliated' +keywords." (let ((case-fold-search t)) (if (not (save-excursion - (re-search-forward "^[ \t]*#\\+END_EXAMPLE" limit t))) + (re-search-forward "^[ \t]*#\\+END_EXAMPLE[ \t]*$" limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((contents-end (match-beginning 0))) (save-excursion (let* ((switches - (progn (looking-at "^[ \t]*#\\+BEGIN_EXAMPLE\\(?: +\\(.*\\)\\)?") - (org-match-string-no-properties 1))) + (progn + (looking-at "^[ \t]*#\\+BEGIN_EXAMPLE\\(?: +\\(.*\\)\\)?") + (org-match-string-no-properties 1))) ;; Switches analysis - (number-lines (cond ((not switches) nil) - ((string-match "-n\\>" switches) 'new) - ((string-match "+n\\>" switches) 'continued))) - (preserve-indent (and switches (string-match "-i\\>" switches))) + (number-lines + (cond ((not switches) nil) + ((string-match "-n\\>" switches) 'new) + ((string-match "+n\\>" switches) 'continued))) + (preserve-indent + (or org-src-preserve-indentation + (and switches (string-match "-i\\>" switches)))) ;; Should labels be retained in (or stripped from) example ;; blocks? (retain-labels @@ -1493,21 +1724,29 @@ containing `:begin', `:end', `:number-lines', `:preserve-indent', ;; line-numbers? (use-labels (or (not switches) - (and retain-labels (not (string-match "-k\\>" switches))))) - (label-fmt (and switches - (string-match "-l +\"\\([^\"\n]+\\)\"" switches) - (match-string 1 switches))) + (and retain-labels + (not (string-match "-k\\>" switches))))) + (label-fmt + (and switches + (string-match "-l +\"\\([^\"\n]+\\)\"" switches) + (match-string 1 switches))) ;; Standard block parsing. - (keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (begin (car affiliated)) + (post-affiliated (point)) + (block-ind (progn (skip-chars-forward " \t") (current-column))) (contents-begin (progn (forward-line) (point))) (hidden (org-invisible-p2)) - (value (buffer-substring-no-properties contents-begin contents-end)) + (value (org-element--remove-indentation + (org-unescape-code-in-string + (buffer-substring-no-properties + contents-begin contents-end)) + (and preserve-indent block-ind))) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'example-block (nconc (list :begin begin @@ -1520,49 +1759,55 @@ containing `:begin', `:end', `:number-lines', `:preserve-indent', :use-labels use-labels :label-fmt label-fmt :hiddenp hidden - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-example-block-interpreter (example-block contents) "Interpret EXAMPLE-BLOCK element as Org syntax. CONTENTS is nil." (let ((switches (org-element-property :switches example-block))) (concat "#+BEGIN_EXAMPLE" (and switches (concat " " switches)) "\n" - (org-remove-indentation + (org-escape-code-in-string (org-element-property :value example-block)) "#+END_EXAMPLE"))) ;;;; Export Block -(defun org-element-export-block-parser (limit) +(defun org-element-export-block-parser (limit affiliated) "Parse an export block. -LIMIT bounds the search. +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. Return a list whose CAR is `export-block' and CDR is a plist -containing `:begin', `:end', `:type', `:hiddenp', `:value' and -`:post-blank' keywords. +containing `:begin', `:end', `:type', `:hiddenp', `:value', +`:post-blank' and `:post-affiliated' keywords. Assume point is at export-block beginning." (let* ((case-fold-search t) (type (progn (looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)") (upcase (org-match-string-no-properties 1))))) (if (not (save-excursion - (re-search-forward (concat "^[ \t]*#\\+END_" type) limit t))) + (re-search-forward + (format "^[ \t]*#\\+END_%s[ \t]*$" type) limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((contents-end (match-beginning 0))) (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) (contents-begin (progn (forward-line) (point))) (hidden (org-invisible-p2)) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol)))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position)))) (value (buffer-substring-no-properties contents-begin contents-end))) (list 'export-block @@ -1572,8 +1817,9 @@ Assume point is at export-block beginning." :type type :value value :hiddenp hidden - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-export-block-interpreter (export-block contents) "Interpret EXPORT-BLOCK element as Org syntax. @@ -1586,18 +1832,22 @@ CONTENTS is nil." ;;;; Fixed-width -(defun org-element-fixed-width-parser (limit) +(defun org-element-fixed-width-parser (limit affiliated) "Parse a fixed-width section. -LIMIT bounds the search. +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. Return a list whose CAR is `fixed-width' and CDR is a plist -containing `:begin', `:end', `:value' and `:post-blank' keywords. +containing `:begin', `:end', `:value', `:post-blank' and +`:post-affiliated' keywords. Assume point is at the beginning of the fixed-width area." (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) value (end-area (progn @@ -1612,43 +1862,54 @@ Assume point is at the beginning of the fixed-width area." (forward-line)) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'fixed-width (nconc (list :begin begin :end end :value value - :post-blank (count-lines end-area end)) - (cadr keywords)))))) + :post-blank (count-lines end-area end) + :post-affiliated post-affiliated) + (cdr affiliated)))))) (defun org-element-fixed-width-interpreter (fixed-width contents) "Interpret FIXED-WIDTH element as Org syntax. CONTENTS is nil." - (replace-regexp-in-string - "^" ": " (substring (org-element-property :value fixed-width) 0 -1))) + (let ((value (org-element-property :value fixed-width))) + (and value + (replace-regexp-in-string + "^" ": " + (if (string-match "\n\\'" value) (substring value 0 -1) value))))) ;;;; Horizontal Rule -(defun org-element-horizontal-rule-parser (limit) +(defun org-element-horizontal-rule-parser (limit affiliated) "Parse an horizontal rule. -LIMIT bounds the search. +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. Return a list whose CAR is `horizontal-rule' and CDR is a plist -containing `:begin', `:end' and `:post-blank' keywords." +containing `:begin', `:end', `:post-blank' and `:post-affiliated' +keywords." (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) - (post-hr (progn (forward-line) (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (let ((begin (car affiliated)) + (post-affiliated (point)) + (post-hr (progn (forward-line) (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'horizontal-rule (nconc (list :begin begin :end end - :post-blank (count-lines post-hr end)) - (cadr keywords)))))) + :post-blank (count-lines post-hr end) + :post-affiliated post-affiliated) + (cdr affiliated)))))) (defun org-element-horizontal-rule-interpreter (horizontal-rule contents) "Interpret HORIZONTAL-RULE element as Org syntax. @@ -1658,30 +1919,37 @@ CONTENTS is nil." ;;;; Keyword -(defun org-element-keyword-parser (limit) +(defun org-element-keyword-parser (limit affiliated) "Parse a keyword at point. -LIMIT bounds the search. +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. Return a list whose CAR is `keyword' and CDR is a plist -containing `:key', `:value', `:begin', `:end' and `:post-blank' -keywords." +containing `:key', `:value', `:begin', `:end', `:post-blank' and +`:post-affiliated' keywords." (save-excursion - (let* ((case-fold-search t) - (begin (point)) - (key (progn (looking-at "[ \t]*#\\+\\(\\S-+\\):") - (upcase (org-match-string-no-properties 1)))) - (value (org-trim (buffer-substring-no-properties - (match-end 0) (point-at-eol)))) - (pos-before-blank (progn (forward-line) (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (let ((begin (car affiliated)) + (post-affiliated (point)) + (key (progn (looking-at "[ \t]*#\\+\\(\\S-+*\\):") + (upcase (org-match-string-no-properties 1)))) + (value (org-trim (buffer-substring-no-properties + (match-end 0) (point-at-eol)))) + (pos-before-blank (progn (forward-line) (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'keyword - (list :key key - :value value - :begin begin - :end end - :post-blank (count-lines pos-before-blank end)))))) + (nconc + (list :key key + :value value + :begin begin + :end end + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated)))))) (defun org-element-keyword-interpreter (keyword contents) "Interpret KEYWORD element as Org syntax. @@ -1693,37 +1961,42 @@ CONTENTS is nil." ;;;; Latex Environment -(defun org-element-latex-environment-parser (limit) +(defun org-element-latex-environment-parser (limit affiliated) "Parse a LaTeX environment. -LIMIT bounds the search. +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. Return a list whose CAR is `latex-environment' and CDR is a plist -containing `:begin', `:end', `:value' and `:post-blank' -keywords. +containing `:begin', `:end', `:value', `:post-blank' and +`:post-affiliated' keywords. Assume point is at the beginning of the latex environment." (save-excursion - (let* ((case-fold-search t) - (code-begin (point)) - (keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) - (env (progn (looking-at "^[ \t]*\\\\begin{\\([A-Za-z0-9]+\\*?\\)}") - (regexp-quote (match-string 1)))) - (code-end - (progn (re-search-forward (format "^[ \t]*\\\\end{%s}" env) limit t) - (forward-line) - (point))) - (value (buffer-substring-no-properties code-begin code-end)) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) - (list 'latex-environment - (nconc - (list :begin begin - :end end - :value value - :post-blank (count-lines code-end end)) - (cadr keywords)))))) + (let ((case-fold-search t) + (code-begin (point))) + (looking-at "[ \t]*\\\\begin{\\([A-Za-z0-9]+\\*?\\)}") + (if (not (re-search-forward (format "^[ \t]*\\\\end{%s}[ \t]*$" + (regexp-quote (match-string 1))) + limit t)) + ;; Incomplete latex environment: parse it as a paragraph. + (org-element-paragraph-parser limit affiliated) + (let* ((code-end (progn (forward-line) (point))) + (begin (car affiliated)) + (value (buffer-substring-no-properties code-begin code-end)) + (end (progn (skip-chars-forward " \r\t\n" limit) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) + (list 'latex-environment + (nconc + (list :begin begin + :end end + :value value + :post-blank (count-lines code-end end) + :post-affiliated code-begin) + (cdr affiliated)))))))) (defun org-element-latex-environment-interpreter (latex-environment contents) "Interpret LATEX-ENVIRONMENT element as Org syntax. @@ -1731,90 +2004,132 @@ CONTENTS is nil." (org-element-property :value latex-environment)) +;;;; Node Property + +(defun org-element-node-property-parser (limit) + "Parse a node-property at point. + +LIMIT bounds the search. + +Return a list whose CAR is `node-property' and CDR is a plist +containing `:key', `:value', `:begin', `:end' and `:post-blank' +keywords." + (save-excursion + (let ((case-fold-search t) + (begin (point)) + (key (progn (looking-at "[ \t]*:\\(.*?\\):[ \t]+\\(.*?\\)[ \t]*$") + (org-match-string-no-properties 1))) + (value (org-match-string-no-properties 2)) + (pos-before-blank (progn (forward-line) (point))) + (end (progn (skip-chars-forward " \r\t\n" limit) + (if (eobp) (point) (point-at-bol))))) + (list 'node-property + (list :key key + :value value + :begin begin + :end end + :post-blank (count-lines pos-before-blank end)))))) + +(defun org-element-node-property-interpreter (node-property contents) + "Interpret NODE-PROPERTY element as Org syntax. +CONTENTS is nil." + (format org-property-format + (format ":%s:" (org-element-property :key node-property)) + (org-element-property :value node-property))) + + ;;;; Paragraph -(defun org-element-paragraph-parser (limit) +(defun org-element-paragraph-parser (limit affiliated) "Parse a paragraph. -LIMIT bounds the search. +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. Return a list whose CAR is `paragraph' and CDR is a plist containing `:begin', `:end', `:contents-begin' and -`:contents-end' and `:post-blank' keywords. +`:contents-end', `:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the paragraph." (save-excursion - (let* (;; INNER-PAR-P is non-nil when paragraph is at the - ;; beginning of an item or a footnote reference. In that - ;; case, we mustn't look for affiliated keywords since they - ;; belong to the container. - (inner-par-p (not (bolp))) + (let* ((begin (car affiliated)) (contents-begin (point)) - (keywords (unless inner-par-p - (org-element--collect-affiliated-keywords))) - (begin (if inner-par-p contents-begin (car keywords))) (before-blank (let ((case-fold-search t)) (end-of-line) - (re-search-forward org-element-paragraph-separate limit 'm) - (while (and (/= (point) limit) - (cond - ;; Skip non-existent or incomplete drawer. - ((save-excursion - (beginning-of-line) - (and (looking-at "[ \t]*:\\S-") - (or (not (looking-at org-drawer-regexp)) - (not (save-excursion - (re-search-forward - "^[ \t]*:END:" limit t))))))) - ;; Stop at comments. - ((save-excursion - (beginning-of-line) - (not (looking-at "[ \t]*#\\S-"))) nil) - ;; Skip incomplete dynamic blocks. - ((save-excursion - (beginning-of-line) - (looking-at "[ \t]*#\\+BEGIN: ")) - (not (save-excursion - (re-search-forward - "^[ \t]*\\+END:" limit t)))) - ;; Skip incomplete blocks. - ((save-excursion - (beginning-of-line) - (looking-at "[ \t]*#\\+BEGIN_\\(\\S-+\\)")) - (not (save-excursion - (re-search-forward - (concat "^[ \t]*#\\+END_" - (match-string 1)) - limit t)))) - ;; Skip incomplete latex environments. - ((save-excursion - (beginning-of-line) - (looking-at "^[ \t]*\\\\begin{\\([A-Za-z0-9]+\\*?\\)}")) - (not (save-excursion - (re-search-forward - (format "^[ \t]*\\\\end{%s}" - (match-string 1)) - limit t)))) - ;; Skip ill-formed keywords. - ((not (save-excursion - (beginning-of-line) - (looking-at "[ \t]*#\\+\\S-+:")))))) - (re-search-forward org-element-paragraph-separate limit 'm)) - (if (eobp) (point) (goto-char (line-beginning-position))))) + (if (not (re-search-forward + org-element-paragraph-separate limit 'm)) + limit + ;; A matching `org-element-paragraph-separate' is not + ;; necessarily the end of the paragraph. In + ;; particular, lines starting with # or : as a first + ;; non-space character are ambiguous. We have check + ;; if they are valid Org syntax (i.e. not an + ;; incomplete keyword). + (beginning-of-line) + (while (not + (or + ;; There's no ambiguity for other symbols or + ;; empty lines: stop here. + (looking-at "[ \t]*\\(?:[^:#]\\|$\\)") + ;; Stop at valid fixed-width areas. + (looking-at "[ \t]*:\\(?: \\|$\\)") + ;; Stop at drawers. + (and (looking-at org-drawer-regexp) + (save-excursion + (re-search-forward + "^[ \t]*:END:[ \t]*$" limit t))) + ;; Stop at valid comments. + (looking-at "[ \t]*#\\(?: \\|$\\)") + ;; Stop at valid dynamic blocks. + (and (looking-at org-dblock-start-re) + (save-excursion + (re-search-forward + "^[ \t]*#\\+END:?[ \t]*$" limit t))) + ;; Stop at valid blocks. + (and (looking-at + "[ \t]*#\\+BEGIN_\\(\\S-+\\)") + (save-excursion + (re-search-forward + (format "^[ \t]*#\\+END_%s[ \t]*$" + (match-string 1)) + limit t))) + ;; Stop at valid latex environments. + (and (looking-at + "^[ \t]*\\\\begin{\\([A-Za-z0-9]+\\*?\\)}[ \t]*$") + (save-excursion + (re-search-forward + (format "^[ \t]*\\\\end{%s}[ \t]*$" + (match-string 1)) + limit t))) + ;; Stop at valid keywords. + (looking-at "[ \t]*#\\+\\S-+:") + ;; Skip everything else. + (not + (progn + (end-of-line) + (re-search-forward org-element-paragraph-separate + limit 'm))))) + (beginning-of-line))) + (if (= (point) limit) limit + (goto-char (line-beginning-position))))) (contents-end (progn (skip-chars-backward " \r\t\n" contents-begin) (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'paragraph (nconc (list :begin begin :end end :contents-begin contents-begin :contents-end contents-end - :post-blank (count-lines before-blank end)) - (cadr keywords)))))) + :post-blank (count-lines before-blank end) + :post-affiliated contents-begin) + (cdr affiliated)))))) (defun org-element-paragraph-interpreter (paragraph contents) "Interpret PARAGRAPH element as Org syntax. @@ -1837,18 +2152,17 @@ and `:post-blank' keywords." (begin (point)) (post-blank (let ((before-blank (progn (forward-line) (point)))) (skip-chars-forward " \r\t\n" limit) - (unless (eobp) (beginning-of-line)) + (skip-chars-backward " \t") + (unless (bolp) (end-of-line)) (count-lines before-blank (point)))) (end (point)) closed deadline scheduled) (goto-char begin) - (while (re-search-forward org-keyword-time-not-clock-regexp - (line-end-position) t) + (while (re-search-forward org-keyword-time-not-clock-regexp end t) (goto-char (match-end 1)) - (org-skip-whitespace) - (let ((time (buffer-substring-no-properties - (1+ (point)) (1- (match-end 0)))) - (keyword (match-string 1))) + (skip-chars-forward " \t" end) + (let ((keyword (match-string 1)) + (time (org-element-timestamp-parser))) (cond ((equal keyword org-closed-string) (setq closed time)) ((equal keyword org-deadline-string) (setq deadline time)) (t (setq scheduled time))))) @@ -1866,68 +2180,21 @@ CONTENTS is nil." (mapconcat 'identity (delq nil - (list (let ((closed (org-element-property :closed planning))) - (when closed (concat org-closed-string " [" closed "]"))) - (let ((deadline (org-element-property :deadline planning))) - (when deadline (concat org-deadline-string " <" deadline ">"))) + (list (let ((deadline (org-element-property :deadline planning))) + (when deadline + (concat org-deadline-string " " + (org-element-timestamp-interpreter deadline nil)))) (let ((scheduled (org-element-property :scheduled planning))) (when scheduled - (concat org-scheduled-string " <" scheduled ">"))))) + (concat org-scheduled-string " " + (org-element-timestamp-interpreter scheduled nil)))) + (let ((closed (org-element-property :closed planning))) + (when closed + (concat org-closed-string " " + (org-element-timestamp-interpreter closed nil)))))) " ")) -;;;; Property Drawer - -(defun org-element-property-drawer-parser (limit) - "Parse a property drawer. - -LIMIT bounds the search. - -Return a list whose CAR is `property-drawer' and CDR is a plist -containing `:begin', `:end', `:hiddenp', `:contents-begin', -`:contents-end', `:properties' and `:post-blank' keywords. - -Assume point is at the beginning of the property drawer." - (save-excursion - (let ((case-fold-search t) - (begin (point)) - (prop-begin (progn (forward-line) (point))) - (hidden (org-invisible-p2)) - (properties - (let (val) - (while (not (looking-at "^[ \t]*:END:")) - (when (looking-at "[ \t]*:\\([A-Za-z][-_A-Za-z0-9]*\\):") - (push (cons (org-match-string-no-properties 1) - (org-trim - (buffer-substring-no-properties - (match-end 0) (point-at-eol)))) - val)) - (forward-line)) - val)) - (prop-end (progn (re-search-forward "^[ \t]*:END:" limit t) - (point-at-bol))) - (pos-before-blank (progn (forward-line) (point))) - (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) - (list 'property-drawer - (list :begin begin - :end end - :hiddenp hidden - :properties properties - :post-blank (count-lines pos-before-blank end)))))) - -(defun org-element-property-drawer-interpreter (property-drawer contents) - "Interpret PROPERTY-DRAWER element as Org syntax. -CONTENTS is nil." - (let ((props (org-element-property :properties property-drawer))) - (concat - ":PROPERTIES:\n" - (mapconcat (lambda (p) - (format org-property-format (format ":%s:" (car p)) (cdr p))) - (nreverse props) "\n") - "\n:END:"))) - - ;;;; Quote Section (defun org-element-quote-section-parser (limit) @@ -1961,27 +2228,30 @@ CONTENTS is nil." ;;;; Src Block -(defun org-element-src-block-parser (limit) +(defun org-element-src-block-parser (limit affiliated) "Parse a src block. -LIMIT bounds the search. +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. Return a list whose CAR is `src-block' and CDR is a plist containing `:language', `:switches', `:parameters', `:begin', `:end', `:hiddenp', `:number-lines', `:retain-labels', -`:use-labels', `:label-fmt', `:preserve-indent', `:value' and -`:post-blank' keywords. +`:use-labels', `:label-fmt', `:preserve-indent', `:value', +`:post-blank' and `:post-affiliated' keywords. Assume point is at the beginning of the block." (let ((case-fold-search t)) - (if (not (save-excursion (re-search-forward "^[ \t]*#\\+END_SRC" limit t))) + (if (not (save-excursion (re-search-forward "^[ \t]*#\\+END_SRC[ \t]*$" + limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((contents-end (match-beginning 0))) (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - ;; Get beginning position. - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) ;; Get language as a string. (language (progn @@ -1996,13 +2266,17 @@ Assume point is at the beginning of the block." ;; Get parameters. (parameters (org-match-string-no-properties 3)) ;; Switches analysis - (number-lines (cond ((not switches) nil) - ((string-match "-n\\>" switches) 'new) - ((string-match "+n\\>" switches) 'continued))) - (preserve-indent (and switches (string-match "-i\\>" switches))) - (label-fmt (and switches - (string-match "-l +\"\\([^\"\n]+\\)\"" switches) - (match-string 1 switches))) + (number-lines + (cond ((not switches) nil) + ((string-match "-n\\>" switches) 'new) + ((string-match "+n\\>" switches) 'continued))) + (preserve-indent (or org-src-preserve-indentation + (and switches + (string-match "-i\\>" switches)))) + (label-fmt + (and switches + (string-match "-l +\"\\([^\"\n]+\\)\"" switches) + (match-string 1 switches))) ;; Should labels be retained in (or stripped from) ;; src blocks? (retain-labels @@ -2013,17 +2287,25 @@ Assume point is at the beginning of the block." ;; line-numbers? (use-labels (or (not switches) - (and retain-labels (not (string-match "-k\\>" switches))))) + (and retain-labels + (not (string-match "-k\\>" switches))))) + ;; Indentation. + (block-ind (progn (skip-chars-forward " \t") (current-column))) ;; Get visibility status. (hidden (progn (forward-line) (org-invisible-p2))) ;; Retrieve code. - (value (buffer-substring-no-properties (point) contents-end)) + (value (org-element--remove-indentation + (org-unescape-code-in-string + (buffer-substring-no-properties + (point) contents-end)) + (and preserve-indent block-ind))) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) ;; Get position after ending blank lines. (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'src-block (nconc (list :language language @@ -2040,8 +2322,9 @@ Assume point is at the beginning of the block." :label-fmt label-fmt :hiddenp hidden :value value - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-src-block-interpreter (src-block contents) "Interpret SRC-BLOCK element as Org syntax. @@ -2051,43 +2334,46 @@ CONTENTS is nil." (params (org-element-property :parameters src-block)) (value (let ((val (org-element-property :value src-block))) (cond - - (org-src-preserve-indentation val) - ((zerop org-edit-src-content-indentation) - (org-remove-indentation val)) + ((org-element-property :preserve-indent src-block) val) + ((zerop org-edit-src-content-indentation) val) (t (let ((ind (make-string org-edit-src-content-indentation 32))) (replace-regexp-in-string - "\\(^\\)[ \t]*\\S-" ind - (org-remove-indentation val) nil nil 1))))))) + "\\(^\\)[ \t]*\\S-" ind val nil nil 1))))))) (concat (format "#+BEGIN_SRC%s\n" (concat (and lang (concat " " lang)) (and switches (concat " " switches)) (and params (concat " " params)))) - value + (org-escape-code-in-string value) "#+END_SRC"))) ;;;; Table -(defun org-element-table-parser (limit) +(defun org-element-table-parser (limit affiliated) "Parse a table at point. -LIMIT bounds the search. +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. Return a list whose CAR is `table' and CDR is a plist containing `:begin', `:end', `:tblfm', `:type', `:contents-begin', -`:contents-end', `:value' and `:post-blank' keywords. +`:contents-end', `:value', `:post-blank' and `:post-affiliated' +keywords. Assume point is at the beginning of the table." (save-excursion (let* ((case-fold-search t) (table-begin (point)) (type (if (org-at-table.el-p) 'table.el 'org)) - (keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) - (table-end (goto-char (marker-position (org-table-end t)))) + (begin (car affiliated)) + (table-end + (if (re-search-forward org-table-any-border-regexp limit 'm) + (goto-char (match-beginning 0)) + (point))) (tblfm (let (acc) (while (looking-at "[ \t]*#\\+TBLFM: +\\(.*\\)[ \t]*$") (push (org-match-string-no-properties 1) acc) @@ -2095,7 +2381,8 @@ Assume point is at the beginning of the table." acc)) (pos-before-blank (point)) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'table (nconc (list :begin begin @@ -2110,8 +2397,9 @@ Assume point is at the beginning of the table." :value (and (eq type 'table.el) (buffer-substring-no-properties table-begin table-end)) - :post-blank (count-lines pos-before-blank end)) - (cadr keywords)))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated table-begin) + (cdr affiliated)))))) (defun org-element-table-interpreter (table contents) "Interpret TABLE element as Org syntax. @@ -2167,32 +2455,36 @@ CONTENTS is the contents of the table row." ;;;; Verse Block -(defun org-element-verse-block-parser (limit) +(defun org-element-verse-block-parser (limit affiliated) "Parse a verse block. -LIMIT bounds the search. +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. Return a list whose CAR is `verse-block' and CDR is a plist containing `:begin', `:end', `:contents-begin', `:contents-end', -`:hiddenp' and `:post-blank' keywords. +`:hiddenp', `:post-blank' and `:post-affiliated' keywords. Assume point is at beginning of the block." (let ((case-fold-search t)) (if (not (save-excursion - (re-search-forward "^[ \t]*#\\+END_VERSE" limit t))) + (re-search-forward "^[ \t]*#\\+END_VERSE[ \t]*$" limit t))) ;; Incomplete block: parse it as a paragraph. - (org-element-paragraph-parser limit) + (org-element-paragraph-parser limit affiliated) (let ((contents-end (match-beginning 0))) (save-excursion - (let* ((keywords (org-element--collect-affiliated-keywords)) - (begin (car keywords)) + (let* ((begin (car affiliated)) + (post-affiliated (point)) (hidden (progn (forward-line) (org-invisible-p2))) (contents-begin (point)) (pos-before-blank (progn (goto-char contents-end) (forward-line) (point))) (end (progn (skip-chars-forward " \r\t\n" limit) - (if (eobp) (point) (point-at-bol))))) + (skip-chars-backward " \t") + (if (bolp) (point) (line-end-position))))) (list 'verse-block (nconc (list :begin begin @@ -2200,8 +2492,9 @@ Assume point is at beginning of the block." :contents-begin contents-begin :contents-end contents-end :hiddenp hidden - :post-blank (count-lines pos-before-blank end)) - (cadr keywords))))))))) + :post-blank (count-lines pos-before-blank end) + :post-affiliated post-affiliated) + (cdr affiliated))))))))) (defun org-element-verse-block-interpreter (verse-block contents) "Interpret VERSE-BLOCK element as Org syntax. @@ -2367,6 +2660,7 @@ LIMIT bounds the search. Return value is a cons cell whose CAR is `entity' or `latex-fragment' and CDR is beginning position." (save-excursion + (unless (bolp) (backward-char)) (let ((matchers (remove "begin" (plist-get org-format-latex-options :matchers))) ;; ENTITY-RE matches both LaTeX commands and Org entities. @@ -2702,12 +2996,15 @@ Return a list whose CAR is `line-break', and CDR a plist with `:begin', `:end' and `:post-blank' keywords. Assume point is at the beginning of the line break." - (list 'line-break (list :begin (point) :end (point-at-eol) :post-blank 0))) + (list 'line-break + (list :begin (point) + :end (progn (forward-line) (point)) + :post-blank 0))) (defun org-element-line-break-interpreter (line-break contents) "Interpret LINE-BREAK object as Org syntax. CONTENTS is nil." - "\\\\") + "\\\\\n") (defun org-element-line-break-successor (limit) "Search for the next line-break object. @@ -2730,14 +3027,15 @@ beginning position." "Parse link at point. Return a list whose CAR is `link' and CDR a plist with `:type', -`:path', `:raw-link', `:begin', `:end', `:contents-begin', -`:contents-end' and `:post-blank' as keywords. +`:path', `:raw-link', `:application', `:search-option', `:begin', +`:end', `:contents-begin', `:contents-end' and `:post-blank' as +keywords. Assume point is at the beginning of the link." (save-excursion (let ((begin (point)) end contents-begin contents-end link-end post-blank path type - raw-link link) + raw-link link search-option application) (cond ;; Type 1: Text targeted from a radio target. ((and org-target-link-regexp (looking-at org-target-link-regexp)) @@ -2749,53 +3047,70 @@ Assume point is at the beginning of the link." (setq contents-begin (match-beginning 3) contents-end (match-end 3) link-end (match-end 0) - ;; RAW-LINK is the original link. - raw-link (org-match-string-no-properties 1) - link (org-translate-link - (org-link-expand-abbrev - (org-link-unescape raw-link)))) + ;; RAW-LINK is the original link. Expand any + ;; abbreviation in it. + raw-link (org-translate-link + (org-link-expand-abbrev + (org-match-string-no-properties 1)))) ;; Determine TYPE of link and set PATH accordingly. (cond ;; File type. - ((or (file-name-absolute-p link) (string-match "^\\.\\.?/" link)) - (setq type "file" path link)) + ((or (file-name-absolute-p raw-link) + (string-match "^\\.\\.?/" raw-link)) + (setq type "file" path raw-link)) ;; Explicit type (http, irc, bbdb...). See `org-link-types'. - ((string-match org-link-re-with-space3 link) - (setq type (match-string 1 link) path (match-string 2 link))) + ((string-match org-link-re-with-space3 raw-link) + (setq type (match-string 1 raw-link) path (match-string 2 raw-link))) ;; Id type: PATH is the id. - ((string-match "^id:\\([-a-f0-9]+\\)" link) - (setq type "id" path (match-string 1 link))) + ((string-match "^id:\\([-a-f0-9]+\\)" raw-link) + (setq type "id" path (match-string 1 raw-link))) ;; Code-ref type: PATH is the name of the reference. - ((string-match "^(\\(.*\\))$" link) - (setq type "coderef" path (match-string 1 link))) + ((string-match "^(\\(.*\\))$" raw-link) + (setq type "coderef" path (match-string 1 raw-link))) ;; Custom-id type: PATH is the name of the custom id. - ((= (aref link 0) ?#) - (setq type "custom-id" path (substring link 1))) + ((= (aref raw-link 0) ?#) + (setq type "custom-id" path (substring raw-link 1))) ;; Fuzzy type: Internal link either matches a target, an ;; headline name or nothing. PATH is the target or ;; headline's name. - (t (setq type "fuzzy" path link)))) + (t (setq type "fuzzy" path raw-link)))) ;; Type 3: Plain link, i.e. http://orgmode.org ((looking-at org-plain-link-re) (setq raw-link (org-match-string-no-properties 0) type (org-match-string-no-properties 1) - path (org-match-string-no-properties 2) - link-end (match-end 0))) + link-end (match-end 0) + path (org-match-string-no-properties 2))) ;; Type 4: Angular link, i.e. ((looking-at org-angle-link-re) (setq raw-link (buffer-substring-no-properties (match-beginning 1) (match-end 2)) type (org-match-string-no-properties 1) - path (org-match-string-no-properties 2) - link-end (match-end 0)))) + link-end (match-end 0) + path (org-match-string-no-properties 2)))) ;; In any case, deduce end point after trailing white space from ;; LINK-END variable. (setq post-blank (progn (goto-char link-end) (skip-chars-forward " \t")) end (point)) + ;; Extract search option and opening application out of + ;; "file"-type links. + (when (member type org-element-link-type-is-file) + ;; Application. + (cond ((string-match "^file\\+\\(.*\\)$" type) + (setq application (match-string 1 type))) + ((not (string-match "^file" type)) + (setq application type))) + ;; Extract search option from PATH. + (when (string-match "::\\(.*\\)$" path) + (setq search-option (match-string 1 path) + path (replace-match "" nil nil path))) + ;; Make sure TYPE always reports "file". + (setq type "file")) (list 'link (list :type type :path path :raw-link (or raw-link path) + :application application + :search-option search-option :begin begin :end end :contents-begin contents-begin @@ -2826,6 +3141,16 @@ beginning position." (when (re-search-forward link-regexp limit t) (cons 'link (match-beginning 0)))))) +(defun org-element-plain-link-successor (limit) + "Search for the next plain link object. + +LIMIT bounds the search. + +Return value is a cons cell whose CAR is `link' and CDR is +beginning position." + (and (save-excursion (re-search-forward org-plain-link-re limit t)) + (cons 'link (match-beginning 0)))) + ;;;; Macro @@ -2845,17 +3170,19 @@ Assume point is at the macro." (post-blank (progn (goto-char (match-end 0)) (skip-chars-forward " \t"))) (end (point)) - (args (let ((args (org-match-string-no-properties 3)) args2) + (args (let ((args (org-match-string-no-properties 3))) (when args - (setq args (org-split-string args ",")) - (while args - (while (string-match "\\\\\\'" (car args)) - ;; Repair bad splits. - (setcar (cdr args) (concat (substring (car args) 0 -1) - "," (nth 1 args))) - (pop args)) - (push (pop args) args2)) - (mapcar 'org-trim (nreverse args2)))))) + ;; Do not use `org-split-string' since empty + ;; strings are meaningful here. + (split-string + (replace-regexp-in-string + "\\(\\\\*\\)\\(,\\)" + (lambda (str) + (let ((len (length (match-string 1 str)))) + (concat (make-string (/ len 2) ?\\) + (if (zerop (mod len 2)) "\000" ",")))) + args nil t) + "\000"))))) (list 'macro (list :key key :value value @@ -3044,6 +3371,7 @@ LIMIT bounds the search. Return value is a cons cell whose CAR is either `subscript' or `superscript' and CDR is beginning position." (save-excursion + (unless (bolp) (backward-char)) (when (re-search-forward org-match-substring-regexp limit t) (cons (if (string= (match-string 2) "_") 'subscript 'superscript) (match-beginning 2))))) @@ -3118,7 +3446,7 @@ LIMIT bounds the search. Return value is a cons cell whose CAR is `table-cell' and CDR is beginning position." - (when (looking-at "[ \t]*.*?[ \t]+|") (cons 'table-cell (point)))) + (when (looking-at "[ \t]*.*?[ \t]*|") (cons 'table-cell (point)))) ;;;; Target @@ -3172,39 +3500,167 @@ Assume point is at the beginning of the timestamp." (save-excursion (let* ((begin (point)) (activep (eq (char-after) ?<)) - (main-value + (raw-value (progn - (looking-at "[<[]\\(\\(%%\\)?.*?\\)[]>]\\(?:--[<[]\\(.*?\\)[]>]\\)?") - (match-string-no-properties 1))) - (range-end (match-string-no-properties 3)) - (type (cond ((match-string 2) 'diary) - ((and activep range-end) 'active-range) - (activep 'active) - (range-end 'inactive-range) - (t 'inactive))) + (looking-at "\\([<[]\\(%%\\)?.*?\\)[]>]\\(?:--\\([<[].*?[]>]\\)\\)?") + (match-string-no-properties 0))) + (date-start (match-string-no-properties 1)) + (date-end (match-string 3)) + (diaryp (match-beginning 2)) (post-blank (progn (goto-char (match-end 0)) (skip-chars-forward " \t"))) - (end (point))) + (end (point)) + (time-range + (and (not diaryp) + (string-match + "[012]?[0-9]:[0-5][0-9]\\(-\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\)" + date-start) + (cons (string-to-number (match-string 2 date-start)) + (string-to-number (match-string 3 date-start))))) + (type (cond (diaryp 'diary) + ((and activep (or date-end time-range)) 'active-range) + (activep 'active) + ((or date-end time-range) 'inactive-range) + (t 'inactive))) + (repeater-props + (and (not diaryp) + (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)>" + raw-value) + (list + :repeater-type + (let ((type (match-string 1 raw-value))) + (cond ((equal "++" type) 'catch-up) + ((equal ".+" type) 'restart) + (t 'cumulate))) + :repeater-value (string-to-number (match-string 2 raw-value)) + :repeater-unit + (case (string-to-char (match-string 3 raw-value)) + (?h 'hour) (?d 'day) (?w 'week) (?m 'month) (t 'year))))) + year-start month-start day-start hour-start minute-start year-end + month-end day-end hour-end minute-end) + ;; Parse date-start. + (unless diaryp + (let ((date (org-parse-time-string date-start t))) + (setq year-start (nth 5 date) + month-start (nth 4 date) + day-start (nth 3 date) + hour-start (nth 2 date) + minute-start (nth 1 date)))) + ;; Compute date-end. It can be provided directly in time-stamp, + ;; or extracted from time range. Otherwise, it defaults to the + ;; same values as date-start. + (unless diaryp + (let ((date (and date-end (org-parse-time-string date-end t)))) + (setq year-end (or (nth 5 date) year-start) + month-end (or (nth 4 date) month-start) + day-end (or (nth 3 date) day-start) + hour-end (or (nth 2 date) (car time-range) hour-start) + minute-end (or (nth 1 date) (cdr time-range) minute-start)))) (list 'timestamp - (list :type type - :value main-value - :range-end range-end - :begin begin - :end end - :post-blank post-blank))))) + (nconc (list :type type + :raw-value raw-value + :year-start year-start + :month-start month-start + :day-start day-start + :hour-start hour-start + :minute-start minute-start + :year-end year-end + :month-end month-end + :day-end day-end + :hour-end hour-end + :minute-end minute-end + :begin begin + :end end + :post-blank post-blank) + repeater-props))))) (defun org-element-timestamp-interpreter (timestamp contents) "Interpret TIMESTAMP object as Org syntax. CONTENTS is nil." - (let ((type (org-element-property :type timestamp) )) - (concat - (format (if (memq type '(inactive inactive-range)) "[%s]" "<%s>") - (org-element-property :value timestamp)) - (let ((range-end (org-element-property :range-end timestamp))) - (when range-end - (concat "--" - (format (if (eq type 'inactive-range) "[%s]" "<%s>") - range-end))))))) + ;; Use `:raw-value' if specified. + (or (org-element-property :raw-value timestamp) + ;; Otherwise, build timestamp string. + (let* ((repeat-string + (concat + (case (org-element-property :repeater-type timestamp) + (cumulate "+") (catch-up "++") (restart ".+")) + (let ((val (org-element-property :repeater-value timestamp))) + (and val (number-to-string val))) + (case (org-element-property :repeater-unit timestamp) + (hour "h") (day "d") (week "w") (month "m") (year "y")))) + (build-ts-string + ;; Build an Org timestamp string from TIME. ACTIVEP is + ;; non-nil when time stamp is active. If WITH-TIME-P is + ;; non-nil, add a time part. HOUR-END and MINUTE-END + ;; specify a time range in the timestamp. REPEAT-STRING + ;; is the repeater string, if any. + (lambda (time activep &optional with-time-p hour-end minute-end) + (let ((ts (format-time-string + (funcall (if with-time-p 'cdr 'car) + org-time-stamp-formats) + time))) + (when (and hour-end minute-end) + (string-match "[012]?[0-9]:[0-5][0-9]" ts) + (setq ts + (replace-match + (format "\\&-%02d:%02d" hour-end minute-end) + nil nil ts))) + (unless activep (setq ts (format "[%s]" (substring ts 1 -1)))) + (when (org-string-nw-p repeat-string) + (setq ts (concat (substring ts 0 -1) + " " + repeat-string + (substring ts -1)))) + ;; Return value. + ts))) + (type (org-element-property :type timestamp))) + (case type + ((active inactive) + (let* ((minute-start (org-element-property :minute-start timestamp)) + (minute-end (org-element-property :minute-end timestamp)) + (hour-start (org-element-property :hour-start timestamp)) + (hour-end (org-element-property :hour-end timestamp)) + (time-range-p (and hour-start hour-end minute-start minute-end + (or (/= hour-start hour-end) + (/= minute-start minute-end))))) + (funcall + build-ts-string + (encode-time 0 + (or minute-start 0) + (or hour-start 0) + (org-element-property :day-start timestamp) + (org-element-property :month-start timestamp) + (org-element-property :year-start timestamp)) + (eq type 'active) + (and hour-start minute-start) + (and time-range-p hour-end) + (and time-range-p minute-end)))) + ((active-range inactive-range) + (let ((minute-start (org-element-property :minute-start timestamp)) + (minute-end (org-element-property :minute-end timestamp)) + (hour-start (org-element-property :hour-start timestamp)) + (hour-end (org-element-property :hour-end timestamp))) + (concat + (funcall + build-ts-string (encode-time + 0 + (or minute-start 0) + (or hour-start 0) + (org-element-property :day-start timestamp) + (org-element-property :month-start timestamp) + (org-element-property :year-start timestamp)) + (eq type 'active-range) + (and hour-start minute-start)) + "--" + (funcall build-ts-string + (encode-time 0 + (or minute-end 0) + (or hour-end 0) + (org-element-property :day-end timestamp) + (org-element-property :month-end timestamp) + (org-element-property :year-end timestamp)) + (eq type 'active-range) + (and hour-end minute-end))))))))) (defun org-element-timestamp-successor (limit) "Search for the next timestamp object. @@ -3295,8 +3751,8 @@ CONTENTS is nil." ;; `org-element--current-element' makes use of special modes. They ;; are activated for fixed element chaining (i.e. `plain-list' > ;; `item') or fixed conditional element chaining (i.e. `headline' > -;; `section'). Special modes are: `first-section', `section', -;; `quote-section', `item' and `table-row'. +;; `section'). Special modes are: `first-section', `item', +;; `node-property', `quote-section', `section' and `table-row'. (defun org-element--current-element (limit &optional granularity special structure) @@ -3317,8 +3773,8 @@ nil), secondary values will not be parsed, since they only contain objects. Optional argument SPECIAL, when non-nil, can be either -`first-section', `section', `quote-section', `table-row' and -`item'. +`first-section', `item', `node-property', `quote-section', +`section', and `table-row'. If STRUCTURE isn't provided but SPECIAL is set to `item', it will be computed. @@ -3326,13 +3782,6 @@ be computed. This function assumes point is always at the beginning of the element it has to parse." (save-excursion - ;; If point is at an affiliated keyword, try moving to the - ;; beginning of the associated element. If none is found, the - ;; keyword is orphaned and will be treated as plain text. - (when (looking-at org-element--affiliated-re) - (let ((opoint (point))) - (while (looking-at org-element--affiliated-re) (forward-line)) - (when (looking-at "[ \t]*$") (goto-char opoint)))) (let ((case-fold-search t) ;; Determine if parsing depth allows for secondary strings ;; parsing. It only applies to elements referenced in @@ -3344,6 +3793,8 @@ element it has to parse." (org-element-item-parser limit structure raw-secondary-p)) ;; Table Row. ((eq special 'table-row) (org-element-table-row-parser limit)) + ;; Node Property. + ((eq special 'node-property) (org-element-node-property-parser limit)) ;; Headline. ((org-with-limited-levels (org-at-heading-p)) (org-element-headline-parser limit raw-secondary-p)) @@ -3356,180 +3807,145 @@ element it has to parse." limit))) ;; When not at bol, point is at the beginning of an item or ;; a footnote definition: next item is always a paragraph. - ((not (bolp)) (org-element-paragraph-parser limit)) + ((not (bolp)) (org-element-paragraph-parser limit (list (point)))) ;; Planning and Clock. - ((and (looking-at org-planning-or-clock-line-re)) + ((looking-at org-planning-or-clock-line-re) (if (equal (match-string 1) org-clock-string) (org-element-clock-parser limit) (org-element-planning-parser limit))) ;; Inlinetask. ((org-at-heading-p) (org-element-inlinetask-parser limit raw-secondary-p)) - ;; LaTeX Environment. - ((looking-at "[ \t]*\\\\begin{\\([A-Za-z0-9*]+\\)}") - (if (save-excursion - (re-search-forward - (format "[ \t]*\\\\end{%s}[ \t]*" - (regexp-quote (match-string 1))) - nil t)) - (org-element-latex-environment-parser limit) - (org-element-paragraph-parser limit))) - ;; Drawer and Property Drawer. - ((looking-at org-drawer-regexp) - (let ((name (match-string 1))) - (cond - ((not (save-excursion - (re-search-forward "^[ \t]*:END:[ \t]*$" nil t))) - (org-element-paragraph-parser limit)) - ((equal "PROPERTIES" name) - (org-element-property-drawer-parser limit)) - (t (org-element-drawer-parser limit))))) - ;; Fixed Width - ((looking-at "[ \t]*:\\( \\|$\\)") - (org-element-fixed-width-parser limit)) - ;; Inline Comments, Blocks, Babel Calls, Dynamic Blocks and - ;; Keywords. - ((looking-at "[ \t]*#") - (goto-char (match-end 0)) - (cond ((looking-at "\\(?: \\|$\\)") - (beginning-of-line) - (org-element-comment-parser limit)) - ((looking-at "\\+BEGIN_\\(\\S-+\\)") - (beginning-of-line) - (let ((parser (assoc (upcase (match-string 1)) - org-element-block-name-alist))) - (if parser (funcall (cdr parser) limit) - (org-element-special-block-parser limit)))) - ((looking-at "\\+CALL:") - (beginning-of-line) - (org-element-babel-call-parser limit)) - ((looking-at "\\+BEGIN:? ") - (beginning-of-line) - (org-element-dynamic-block-parser limit)) - ((looking-at "\\+\\S-+:") - (beginning-of-line) - (org-element-keyword-parser limit)) - (t - (beginning-of-line) - (org-element-paragraph-parser limit)))) - ;; Footnote Definition. - ((looking-at org-footnote-definition-re) - (org-element-footnote-definition-parser limit)) - ;; Horizontal Rule. - ((looking-at "[ \t]*-\\{5,\\}[ \t]*$") - (org-element-horizontal-rule-parser limit)) - ;; Table. - ((org-at-table-p t) (org-element-table-parser limit)) - ;; List. - ((looking-at (org-item-re)) - (org-element-plain-list-parser limit (or structure (org-list-struct)))) - ;; Default element: Paragraph. - (t (org-element-paragraph-parser limit)))))) + ;; From there, elements can have affiliated keywords. + (t (let ((affiliated (org-element--collect-affiliated-keywords limit))) + (cond + ;; Jumping over affiliated keywords put point off-limits. + ;; Parse them as regular keywords. + ((and (cdr affiliated) (>= (point) limit)) + (goto-char (car affiliated)) + (org-element-keyword-parser limit nil)) + ;; LaTeX Environment. + ((looking-at + "[ \t]*\\\\begin{[A-Za-z0-9*]+}\\(\\[.*?\\]\\|{.*?}\\)*[ \t]*$") + (org-element-latex-environment-parser limit affiliated)) + ;; Drawer and Property Drawer. + ((looking-at org-drawer-regexp) + (if (equal (match-string 1) "PROPERTIES") + (org-element-property-drawer-parser limit affiliated) + (org-element-drawer-parser limit affiliated))) + ;; Fixed Width + ((looking-at "[ \t]*:\\( \\|$\\)") + (org-element-fixed-width-parser limit affiliated)) + ;; Inline Comments, Blocks, Babel Calls, Dynamic Blocks and + ;; Keywords. + ((looking-at "[ \t]*#") + (goto-char (match-end 0)) + (cond ((looking-at "\\(?: \\|$\\)") + (beginning-of-line) + (org-element-comment-parser limit affiliated)) + ((looking-at "\\+BEGIN_\\(\\S-+\\)") + (beginning-of-line) + (let ((parser (assoc (upcase (match-string 1)) + org-element-block-name-alist))) + (if parser (funcall (cdr parser) limit affiliated) + (org-element-special-block-parser limit affiliated)))) + ((looking-at "\\+CALL:") + (beginning-of-line) + (org-element-babel-call-parser limit affiliated)) + ((looking-at "\\+BEGIN:? ") + (beginning-of-line) + (org-element-dynamic-block-parser limit affiliated)) + ((looking-at "\\+\\S-+:") + (beginning-of-line) + (org-element-keyword-parser limit affiliated)) + (t + (beginning-of-line) + (org-element-paragraph-parser limit affiliated)))) + ;; Footnote Definition. + ((looking-at org-footnote-definition-re) + (org-element-footnote-definition-parser limit affiliated)) + ;; Horizontal Rule. + ((looking-at "[ \t]*-\\{5,\\}[ \t]*$") + (org-element-horizontal-rule-parser limit affiliated)) + ;; Diary Sexp. + ((looking-at "%%(") + (org-element-diary-sexp-parser limit affiliated)) + ;; Table. + ((org-at-table-p t) (org-element-table-parser limit affiliated)) + ;; List. + ((looking-at (org-item-re)) + (org-element-plain-list-parser + limit affiliated (or structure (org-list-struct)))) + ;; Default element: Paragraph. + (t (org-element-paragraph-parser limit affiliated))))))))) ;; Most elements can have affiliated keywords. When looking for an ;; element beginning, we want to move before them, as they belong to ;; that element, and, in the meantime, collect information they give ;; into appropriate properties. Hence the following function. -;; -;; Usage of optional arguments may not be obvious at first glance: -;; -;; - TRANS-LIST is used to polish keywords names that have evolved -;; during Org history. In example, even though =result= and -;; =results= coexist, we want to have them under the same =result= -;; property. It's also true for "srcname" and "name", where the -;; latter seems to be preferred nowadays (thus the "name" property). -;; -;; - CONSED allows to regroup multi-lines keywords under the same -;; property, while preserving their own identity. This is mostly -;; used for "attr_latex" and al. -;; -;; - PARSED prepares a keyword value for export. This is useful for -;; "caption". Objects restrictions for such keywords are defined in -;; `org-element-object-restrictions'. -;; -;; - DUALS is used to take care of keywords accepting a main and an -;; optional secondary values. For example "results" has its -;; source's name as the main value, and may have an hash string in -;; optional square brackets as the secondary one. -;; -;; A keyword may belong to more than one category. - -(defun org-element--collect-affiliated-keywords - (&optional key-re trans-list consed parsed duals) - "Collect affiliated keywords before point. - -Optional argument KEY-RE is a regexp matching keywords, which -puts matched keyword in group 1. It defaults to -`org-element--affiliated-re'. -TRANS-LIST is an alist where key is the keyword and value the -property name it should be translated to, without the colons. It -defaults to `org-element-keyword-translation-alist'. - -CONSED is a list of strings. Any keyword belonging to that list -will have its value consed. The check is done after keyword -translation. It defaults to `org-element-multiple-keywords'. - -PARSED is a list of strings. Any keyword member of this list -will have its value parsed. The check is done after keyword -translation. If a keyword is a member of both CONSED and PARSED, -it's value will be a list of parsed strings. It defaults to -`org-element-parsed-keywords'. - -DUALS is a list of strings. Any keyword member of this list can -have two parts: one mandatory and one optional. Its value is -a cons cell whose CAR is the former, and the CDR the latter. If -a keyword is a member of both PARSED and DUALS, both values will -be parsed. It defaults to `org-element-dual-keywords'. +(defun org-element--collect-affiliated-keywords (limit) + "Collect affiliated keywords from point down to LIMIT. Return a list whose CAR is the position at the first of them and -CDR a plist of keywords and values." - (save-excursion +CDR a plist of keywords and values and move point to the +beginning of the first line after them. + +As a special case, if element doesn't start at the beginning of +the line (i.e. a paragraph starting an item), CAR is current +position of point and CDR is nil." + (if (not (bolp)) (list (point)) (let ((case-fold-search t) - (key-re (or key-re org-element--affiliated-re)) - (trans-list (or trans-list org-element-keyword-translation-alist)) - (consed (or consed org-element-multiple-keywords)) - (parsed (or parsed org-element-parsed-keywords)) - (duals (or duals org-element-dual-keywords)) + (origin (point)) ;; RESTRICT is the list of objects allowed in parsed ;; keywords value. (restrict (org-element-restriction 'keyword)) output) - (unless (bobp) - (while (and (not (bobp)) (progn (forward-line -1) (looking-at key-re))) - (let* ((raw-kwd (upcase (match-string 1))) - ;; Apply translation to RAW-KWD. From there, KWD is - ;; the official keyword. - (kwd (or (cdr (assoc raw-kwd trans-list)) raw-kwd)) - ;; Find main value for any keyword. - (value - (save-match-data - (org-trim - (buffer-substring-no-properties - (match-end 0) (point-at-eol))))) - ;; If KWD is a dual keyword, find its secondary - ;; value. Maybe parse it. - (dual-value - (and (member kwd duals) - (let ((sec (org-match-string-no-properties 2))) - (if (or (not sec) (not (member kwd parsed))) sec - (org-element-parse-secondary-string sec restrict))))) - ;; Attribute a property name to KWD. - (kwd-sym (and kwd (intern (concat ":" (downcase kwd)))))) - ;; Now set final shape for VALUE. - (when (member kwd parsed) - (setq value (org-element-parse-secondary-string value restrict))) - (when (member kwd duals) - ;; VALUE is mandatory. Set it to nil if there is none. - (setq value (and value (cons value dual-value)))) - ;; Attributes are always consed. - (when (or (member kwd consed) (string-match "^ATTR_" kwd)) - (setq value (cons value (plist-get output kwd-sym)))) - ;; Eventually store the new value in OUTPUT. - (setq output (plist-put output kwd-sym value)))) - (unless (looking-at key-re) (forward-line 1))) - (list (point) output)))) + (while (and (< (point) limit) (looking-at org-element--affiliated-re)) + (let* ((raw-kwd (upcase (match-string 1))) + ;; Apply translation to RAW-KWD. From there, KWD is + ;; the official keyword. + (kwd (or (cdr (assoc raw-kwd + org-element-keyword-translation-alist)) + raw-kwd)) + ;; Find main value for any keyword. + (value + (save-match-data + (org-trim + (buffer-substring-no-properties + (match-end 0) (point-at-eol))))) + ;; PARSEDP is non-nil when keyword should have its + ;; value parsed. + (parsedp (member kwd org-element-parsed-keywords)) + ;; If KWD is a dual keyword, find its secondary + ;; value. Maybe parse it. + (dualp (member kwd org-element-dual-keywords)) + (dual-value + (and dualp + (let ((sec (org-match-string-no-properties 2))) + (if (or (not sec) (not parsedp)) sec + (org-element-parse-secondary-string sec restrict))))) + ;; Attribute a property name to KWD. + (kwd-sym (and kwd (intern (concat ":" (downcase kwd)))))) + ;; Now set final shape for VALUE. + (when parsedp + (setq value (org-element-parse-secondary-string value restrict))) + (when dualp + (setq value (and (or value dual-value) (cons value dual-value)))) + (when (or (member kwd org-element-multiple-keywords) + ;; Attributes can always appear on multiple lines. + (string-match "^ATTR_" kwd)) + (setq value (cons value (plist-get output kwd-sym)))) + ;; Eventually store the new value in OUTPUT. + (setq output (plist-put output kwd-sym value)) + ;; Move to next keyword. + (forward-line))) + ;; If affiliated keywords are orphaned: move back to first one. + ;; They will be parsed as a paragraph. + (when (looking-at "[ \t]*$") (goto-char origin) (setq output nil)) + ;; Return value. + (cons origin output)))) @@ -3564,7 +3980,32 @@ recursion. It can be set to the following symbols: When VISIBLE-ONLY is non-nil, don't parse contents of hidden elements. -Assume buffer is in Org mode." +An element or an objects is represented as a list with the +pattern (TYPE PROPERTIES CONTENTS), where : + + TYPE is a symbol describing the element or object. See + `org-element-all-elements' and `org-element-all-objects' for an + exhaustive list of such symbols. One can retrieve it with + `org-element-type' function. + + PROPERTIES is the list of attributes attached to the element or + object, as a plist. Although most of them are specific to the + element or object type, all types share `:begin', `:end', + `:post-blank' and `:parent' properties, which respectively + refer to buffer position where the element or object starts, + ends, the number of white spaces or blank lines after it, and + the element or object containing it. Properties values can be + obtained by using `org-element-property' function. + + CONTENTS is a list of elements, objects or raw strings + contained in the current element or object, when applicable. + One can access them with `org-element-contents' function. + +The Org buffer has `org-data' as type and nil as properties. +`org-element-map' function can be used to find specific elements +or objects within the parse tree. + +This function assumes that current major mode is `org-mode'." (save-excursion (goto-char (point-min)) (org-skip-whitespace) @@ -3583,21 +4024,33 @@ looked after. Optional argument PARENT, when non-nil, is the element or object containing the secondary string. It is used to set correctly `:parent' property within the string." - (with-temp-buffer - (insert string) - (let ((secondary (org-element--parse-objects - (point-min) (point-max) nil restriction))) - (mapc (lambda (obj) (org-element-put-property obj :parent parent)) - secondary)))) - -(defun org-element-map (data types fun &optional info first-match no-recursion) + ;; Copy buffer-local variables listed in + ;; `org-element-object-variables' into temporary buffer. This is + ;; required since object parsing is dependent on these variables. + (let ((pairs (delq nil (mapcar (lambda (var) + (when (boundp var) + (cons var (symbol-value var)))) + org-element-object-variables)))) + (with-temp-buffer + (mapc (lambda (pair) (org-set-local (car pair) (cdr pair))) pairs) + (insert string) + (let ((secondary (org-element--parse-objects + (point-min) (point-max) nil restriction))) + (when parent + (mapc (lambda (obj) (org-element-put-property obj :parent parent)) + secondary)) + secondary)))) + +(defun org-element-map + (data types fun &optional info first-match no-recursion with-affiliated) "Map a function on selected elements or objects. -DATA is the parsed tree, as returned by, i.e, -`org-element-parse-buffer'. TYPES is a symbol or list of symbols -of elements or objects types. FUN is the function called on the -matching element or object. It must accept one arguments: the -element or object itself. +DATA is a parse tree, an element, an object, a string, or a list +of such constructs. TYPES is a symbol or list of symbols of +elements or objects types (see `org-element-all-elements' and +`org-element-all-objects' for a complete list of types). FUN is +the function called on the matching element or object. It has to +accept one argument: the element or object itself. When optional argument INFO is non-nil, it should be a plist holding export options. In that case, parts of the parse tree @@ -3611,7 +4064,45 @@ representing elements or objects types. `org-element-map' won't enter any recursive element or object whose type belongs to that list. Though, FUN can still be applied on them. -Nil values returned from FUN do not appear in the results." +When optional argument WITH-AFFILIATED is non-nil, FUN will also +apply to matching objects within parsed affiliated keywords (see +`org-element-parsed-keywords'). + +Nil values returned from FUN do not appear in the results. + + +Examples: +--------- + +Assuming TREE is a variable containing an Org buffer parse tree, +the following example will return a flat list of all `src-block' +and `example-block' elements in it: + + \(org-element-map tree '(example-block src-block) 'identity) + +The following snippet will find the first headline with a level +of 1 and a \"phone\" tag, and will return its beginning position: + + \(org-element-map tree 'headline + \(lambda (hl) + \(and (= (org-element-property :level hl) 1) + \(member \"phone\" (org-element-property :tags hl)) + \(org-element-property :begin hl))) + nil t) + +The next example will return a flat list of all `plain-list' type +elements in TREE that are not a sub-list themselves: + + \(org-element-map tree 'plain-list 'identity nil nil 'plain-list) + +Eventually, this example will return a flat list of all `bold' +type objects containing a `latex-snippet' type object, even +looking into captions: + + \(org-element-map tree 'bold + \(lambda (b) + \(and (org-element-map b 'latex-snippet 'identity nil t) b)) + nil nil nil t)" ;; Ensure TYPES and NO-RECURSION are a list, even of one element. (unless (listp types) (setq types (list types))) (unless (listp no-recursion) (setq no-recursion (list no-recursion))) @@ -3633,6 +4124,12 @@ Nil values returned from FUN do not appear in the results." (setq category 'elements))))) types) category))) + ;; Compute properties for affiliated keywords if necessary. + (--affiliated-alist + (and with-affiliated + (mapcar (lambda (kwd) + (cons kwd (intern (concat ":" (downcase kwd))))) + org-element-affiliated-keywords))) --acc --walk-tree (--walk-tree @@ -3645,9 +4142,8 @@ Nil values returned from FUN do not appear in the results." ((not --data)) ;; Ignored element in an export context. ((and info (memq --data (plist-get info :ignore-list)))) - ;; Secondary string: only objects can be found there. - ((not --type) - (when (eq --category 'objects) (mapc --walk-tree --data))) + ;; List of elements or objects. + ((not --type) (mapc --walk-tree --data)) ;; Unconditionally enter parse trees. ((eq --type 'org-data) (mapc --walk-tree (org-element-contents --data))) @@ -3662,12 +4158,40 @@ Nil values returned from FUN do not appear in the results." (t (push result --acc))))) ;; If --DATA has a secondary string that can contain ;; objects with their type among TYPES, look into it. - (when (eq --category 'objects) + (when (and (eq --category 'objects) (not (stringp --data))) (let ((sec-prop (assq --type org-element-secondary-value-alist))) (when sec-prop (funcall --walk-tree (org-element-property (cdr sec-prop) --data))))) + ;; If --DATA has any affiliated keywords and + ;; WITH-AFFILIATED is non-nil, look for objects in + ;; them. + (when (and with-affiliated + (eq --category 'objects) + (memq --type org-element-all-elements)) + (mapc (lambda (kwd-pair) + (let ((kwd (car kwd-pair)) + (value (org-element-property + (cdr kwd-pair) --data))) + ;; Pay attention to the type of value. + ;; Preserve order for multiple keywords. + (cond + ((not value)) + ((and (member kwd org-element-multiple-keywords) + (member kwd org-element-dual-keywords)) + (mapc (lambda (line) + (funcall --walk-tree (cdr line)) + (funcall --walk-tree (car line))) + (reverse value))) + ((member kwd org-element-multiple-keywords) + (mapc (lambda (line) (funcall --walk-tree line)) + (reverse value))) + ((member kwd org-element-dual-keywords) + (funcall --walk-tree (cdr value)) + (funcall --walk-tree (car value))) + (t (funcall --walk-tree value))))) + --affiliated-alist)) ;; Determine if a recursion into --DATA is possible. (cond ;; --TYPE is explicitly removed from recursion. @@ -3687,6 +4211,7 @@ Nil values returned from FUN do not appear in the results." (funcall --walk-tree data) ;; Return value in a proper order. (nreverse --acc)))) +(put 'org-element-map 'lisp-indent-function 2) ;; The following functions are internal parts of the parser. ;; @@ -3725,6 +4250,10 @@ 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))) @@ -3737,15 +4266,16 @@ Elements are accumulated into ACC." (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 VISIBLE-ONLY is true and element is hidden or if it has - ;; no contents, don't modify it. - ((or (and visible-only (org-element-property :hiddenp element)) - (not cbeg))) + ;; 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 an headline, in which case going + ;; 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)) @@ -3760,8 +4290,10 @@ Elements are accumulated into ACC." (if (org-element-property :quotedp element) 'quote-section 'section)) (plain-list 'item) + (property-drawer 'node-property) (table 'table-row)) - (org-element-property :structure element) + (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. @@ -3778,9 +4310,9 @@ Elements are accumulated into ACC." Objects are accumulated in ACC. -RESTRICTION is a list of object types which are allowed in the -current object." - (let (candidates) +RESTRICTION is a list of object successors which are allowed in +the current object." + (let ((candidates 'initial)) (save-excursion (goto-char beg) (while (and (< (point) end) @@ -3832,35 +4364,35 @@ current object." "Return an alist of candidates for the next object. LIMIT bounds the search, and RESTRICTION narrows candidates to -some object types. - -Return value is an alist whose CAR is position and CDR the object -type, as a symbol. - -OBJECTS is the previous candidates alist." - (let (next-candidates types-to-search) - ;; If no previous result, search every object type in RESTRICTION. - ;; Otherwise, keep potential candidates (old objects located after - ;; point) and ask to search again those which had matched before. - (if (not objects) (setq types-to-search restriction) - (mapc (lambda (obj) - (if (< (cdr obj) (point)) (push (car obj) types-to-search) - (push obj next-candidates))) - objects)) - ;; Call the appropriate successor function for each type to search - ;; and accumulate matches. - (mapc - (lambda (type) - (let* ((successor-fun - (intern - (format "org-element-%s-successor" - (or (cdr (assq type org-element-object-successor-alist)) - type)))) - (obj (funcall successor-fun limit))) - (and obj (push obj next-candidates)))) - types-to-search) - ;; Return alist. - next-candidates)) +some object successors. + +OBJECTS is the previous candidates alist. If it is set to +`initial', no search has been done before, and all symbols in +RESTRICTION should be looked after. + +Return value is an alist whose CAR is the object type and CDR its +beginning position." + (delq + nil + (if (eq objects 'initial) + ;; When searching for the first time, look for every successor + ;; allowed in RESTRICTION. + (mapcar + (lambda (res) + (funcall (intern (format "org-element-%s-successor" res)) limit)) + restriction) + ;; Focus on objects returned during last search. Keep those + ;; still after point. Search again objects before it. + (mapcar + (lambda (obj) + (if (>= (cdr obj) (point)) obj + (let* ((type (car obj)) + (succ (or (cdr (assq type org-element-object-successor-alist)) + type))) + (and succ + (funcall (intern (format "org-element-%s-successor" succ)) + limit))))) + objects)))) @@ -3898,8 +4430,8 @@ Return Org syntax as a string." (mapconcat (lambda (obj) (org-element-interpret-data obj parent)) (org-element-contents data) "")) - ;; Plain text. - ((stringp data) data) + ;; Plain text: remove `:parent' text property from output. + ((stringp data) (org-no-properties data)) ;; Element/Object without contents. ((not (org-element-contents data)) (funcall (intern (format "org-element-%s-interpreter" type)) @@ -3924,7 +4456,7 @@ Return Org syntax as a string." (and (eq type 'paragraph) (equal data (car (org-element-contents parent))) (memq (org-element-type parent) - '(footnote-definiton item)))))) + '(footnote-definition item)))))) ""))) (funcall (intern (format "org-element-%s-interpreter" type)) data @@ -3967,7 +4499,7 @@ If there is no affiliated keyword, return the empty string." ;; All attribute keywords can have multiple lines. (string-match "^ATTR_" keyword)) (mapconcat (lambda (line) (funcall keyword-to-org keyword line)) - value + (reverse value) "") (funcall keyword-to-org keyword value))))) ;; List all ELEMENT's properties matching an attribute line or an @@ -4111,8 +4643,8 @@ of the element and PROPS a plist of properties associated to the 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. +Properties depend on element or object type, but always include +`:begin', `:end', `:parent' and `:post-blank' properties. As a special case, if point is at the very beginning of a list or sub-list, returned element will be that list instead of the first @@ -4121,12 +4653,12 @@ row of a table, returned element will be the table instead of the first row. If optional argument KEEP-TRAIL is non-nil, the function returns -a list of of elements leading to element at point. The list's -CAR is always the element at point. Following positions contain +a list of elements leading to element at point. The list's CAR +is always the element at point. The following positions contain element's siblings, then parents, siblings of parents, until the first element of current section." (org-with-wide-buffer - ;; If at an headline, parse it. It is the sole element that + ;; If at a headline, parse it. It is the sole element that ;; doesn't require to know about context. Be sure to disallow ;; secondary string parsing, though. (if (org-with-limited-levels (org-at-heading-p)) @@ -4136,27 +4668,40 @@ first element of current section." (list (org-element-headline-parser (point-max) t)))) ;; Otherwise move at the beginning of the section containing ;; point. - (let ((origin (point)) - (end (save-excursion - (org-with-limited-levels (outline-next-heading)) (point))) - element type special-flag trail struct prevs parent) - (org-with-limited-levels - (if (org-with-limited-levels (org-before-first-heading-p)) - (goto-char (point-min)) - (org-back-to-heading) - (forward-line))) - (org-skip-whitespace) - (beginning-of-line) - ;; Parse successively each element, skipping those ending - ;; before original position. - (catch 'exit - (while t - (setq element + (catch 'exit + (let ((origin (point)) + (end (save-excursion + (org-with-limited-levels (outline-next-heading)) (point))) + element type special-flag trail struct prevs parent) + (org-with-limited-levels + (if (org-before-first-heading-p) + ;; In empty lines at buffer's beginning, return nil. + (progn (goto-char (point-min)) + (org-skip-whitespace) + (when (or (eobp) (> (line-beginning-position) origin)) + (throw 'exit nil))) + (org-back-to-heading) + (forward-line) + (org-skip-whitespace) + (when (> (line-beginning-position) origin) + ;; In blank lines just after the headline, point still + ;; belongs to the headline. + (throw 'exit + (progn (org-back-to-heading) + (if (not keep-trail) + (org-element-headline-parser (point-max) t) + (list (org-element-headline-parser + (point-max) t)))))))) + (beginning-of-line) + ;; Parse successively each element, skipping those ending + ;; before original position. + (while t + (setq element (org-element--current-element end 'element special-flag struct) - type (car element)) + type (car element)) (org-element-put-property element :parent parent) (when keep-trail (push element trail)) - (cond + (cond ;; 1. Skip any element ending before point. Also skip ;; element ending at point when we're sure that another ;; element has started. @@ -4186,20 +4731,24 @@ first element of current section." (memq type '(center-block drawer dynamic-block inlinetask item - plain-list quote-block special-block)))) + plain-list property-drawer quote-block + special-block)))) (throw 'exit (if keep-trail trail element)) (setq parent element) (case type (plain-list (setq special-flag 'item struct (org-element-property :structure element))) - (table (setq special-flag 'table-row)) - (otherwise (setq special-flag nil))) + (item (setq special-flag nil)) + (property-drawer + (setq special-flag 'node-property struct nil)) + (table (setq special-flag 'table-row struct nil)) + (otherwise (setq special-flag nil struct nil))) (setq end cend) (goto-char cbeg))))))))))) ;;;###autoload -(defun org-element-context () +(defun org-element-context (&optional element) "Return closest element or object around point. Return value is a list like (TYPE PROPS) where TYPE is the type @@ -4208,17 +4757,37 @@ associated to it. Possible types are defined in `org-element-all-elements' and `org-element-all-objects'. Properties depend on element or -object type, but always include :begin, :end, :parent -and :post-blank properties." +object type, but always include `:begin', `:end', `:parent' and +`:post-blank'. + +Optional argument ELEMENT, when non-nil, is the closest element +containing point, as returned by `org-element-at-point'. +Providing it allows for quicker computation." (org-with-wide-buffer (let* ((origin (point)) - (element (org-element-at-point)) - (type (car element)) + (element (or element (org-element-at-point))) + (type (org-element-type element)) end) ;; Check if point is inside an element containing objects or at ;; a secondary string. In that case, move to beginning of the ;; element or secondary string and set END to the other side. - (if (not (or (and (eq type 'item) + (if (not (or (let ((post (org-element-property :post-affiliated element))) + (and post (> post origin) + (< (org-element-property :begin element) origin) + (progn (beginning-of-line) + (looking-at org-element--affiliated-re) + (member (upcase (match-string 1)) + org-element-parsed-keywords)) + ;; We're at an affiliated keyword. Change + ;; type to retrieve correct restrictions. + (setq type 'keyword) + ;; Determine if we're at main or dual value. + (if (and (match-end 2) (<= origin (match-end 2))) + (progn (goto-char (match-beginning 2)) + (setq end (match-end 2))) + (goto-char (match-end 0)) + (setq end (line-end-position))))) + (and (eq type 'item) (let ((tag (org-element-property :tag element))) (and tag (progn @@ -4235,18 +4804,26 @@ and :post-blank properties." (progn (beginning-of-line) (skip-chars-forward "* ") (setq end (point-at-eol)))) - (and (memq type '(paragraph table-cell verse-block)) + (and (memq type '(paragraph table-row verse-block)) (let ((cbeg (org-element-property :contents-begin element)) (cend (org-element-property :contents-end element))) - (and (>= origin cbeg) + (and cbeg cend ; cbeg is nil for table rules + (>= origin cbeg) (<= origin cend) - (progn (goto-char cbeg) (setq end cend))))))) + (progn (goto-char cbeg) (setq end cend))))) + (and (eq type 'keyword) + (let ((key (org-element-property :key element))) + (and (member key org-element-document-properties) + (progn (beginning-of-line) + (search-forward key (line-end-position) t) + (forward-char) + (setq end (line-end-position)))))))) element - (let ((restriction (org-element-restriction element)) + (let ((restriction (org-element-restriction type)) (parent element) - candidates) + (candidates 'initial)) (catch 'exit (while (setq candidates (org-element--get-next-object-candidates end restriction candidates)) @@ -4254,30 +4831,37 @@ and :post-blank properties." candidates))) ;; If ORIGIN is before next object in element, there's ;; no point in looking further. - (if (> (cdr closest-cand) origin) (throw 'exit element) + (if (> (cdr closest-cand) origin) (throw 'exit parent) (let* ((object (progn (goto-char (cdr closest-cand)) (funcall (intern (format "org-element-%s-parser" (car closest-cand)))))) (cbeg (org-element-property :contents-begin object)) - (cend (org-element-property :contents-end object))) + (cend (org-element-property :contents-end object)) + (obj-end (org-element-property :end object))) (cond ;; ORIGIN is after OBJECT, so skip it. - ((< (org-element-property :end object) origin) - (goto-char (org-element-property :end object))) - ;; ORIGIN is within a non-recursive object or at an - ;; object boundaries: Return that object. + ((<= obj-end origin) + (if (/= obj-end end) (goto-char obj-end) + (throw 'exit + (org-element-put-property + object :parent parent)))) + ;; ORIGIN is within a non-recursive object or at + ;; an object boundaries: Return that object. ((or (not cbeg) (> cbeg origin) (< cend origin)) (throw 'exit (org-element-put-property object :parent parent))) - ;; Otherwise, move within current object and restrict - ;; search to the end of its contents. + ;; Otherwise, move within current object and + ;; restrict search to the end of its contents. (t (goto-char cbeg) (org-element-put-property object :parent parent) - (setq parent object end cend))))))) + (setq parent object + restriction (org-element-restriction object) + candidates 'initial + end cend))))))) parent)))))) -(defsubst org-element-nested-p (elem-A elem-B) +(defun org-element-nested-p (elem-A elem-B) "Non-nil when elements ELEM-A and ELEM-B are nested." (let ((beg-A (org-element-property :begin elem-A)) (beg-B (org-element-property :begin elem-B)) @@ -4351,6 +4935,10 @@ end of ELEM-A." (cdr overlays))) (goto-char (org-element-property :end elem-B))))) - (provide 'org-element) + +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-element.el ends here -- cgit v1.2.3