summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien <bzg@gnu.org>2019-12-03 16:36:50 +0100
committerBastien <bzg@gnu.org>2019-12-03 16:36:50 +0100
commit7d3d14daa3bba9e067f20742a01a5c406ce8a852 (patch)
tree2f775afd5505ecbf0fc739b731e7e1ff321193f8
parentefe4c85fa2643fcb0ea98f9e7c463f96c0c142ba (diff)
parentcb8183d3b710416fae0874345ad0ba24130bda30 (diff)
downloadorg-mode-7d3d14daa3bba9e067f20742a01a5c406ce8a852.tar.gz
Merge branch 'next' of code.orgmode.org:bzg/org-mode
-rw-r--r--contrib/lisp/ox-taskjuggler.el2
-rw-r--r--doc/org-manual.org25
-rw-r--r--etc/ORG-NEWS12
-rw-r--r--lisp/org-agenda.el43
-rw-r--r--lisp/org-attach.el2
-rw-r--r--lisp/org-capture.el4
-rw-r--r--lisp/org-compat.el6
-rw-r--r--lisp/org-element.el5
-rw-r--r--lisp/org.el279
-rw-r--r--testing/examples/property-inheritance.org6
-rw-r--r--testing/lisp/test-org-element.el21
-rw-r--r--testing/lisp/test-org.el307
12 files changed, 565 insertions, 147 deletions
diff --git a/contrib/lisp/ox-taskjuggler.el b/contrib/lisp/ox-taskjuggler.el
index 16a6a3a..bc90b93 100644
--- a/contrib/lisp/ox-taskjuggler.el
+++ b/contrib/lisp/ox-taskjuggler.el
@@ -137,7 +137,7 @@
;; :END:
;;
;;;; * TODO
-;; - Look at org-file-properties, org-global-properties and
+;; - Look at org-keyword-properties, org-global-properties and
;; org-global-properties-fixed
;; - What about property inheritance and org-property-inherit-p?
;; - Use TYPE_TODO as an way to assign resources
diff --git a/doc/org-manual.org b/doc/org-manual.org
index 6476126..f4387aa 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -4957,7 +4957,7 @@ with many examples, see [[*Matching tags and properties]].
A property is a key-value pair associated with an entry. Properties
can be set so they are associated with a single entry, with every
-entry in a tree, or with every entry in an Org file.
+entry in a tree, or with the whole buffer.
There are two main applications for properties in Org mode. First,
properties are like tags, but with a value. Imagine maintaining
@@ -5021,8 +5021,12 @@ disks in a box like this:
:END:
#+end_example
-If you want to set properties that can be inherited by any entry in
-a file, use a line like:
+Properties can be inserted on buffer level. That means they apply
+before the first headline and can be inherited by all entries in a
+file. Property blocks defined before first headline needs to be
+located at the top of the buffer, allowing only comments above.
+
+Properties can also be defined using lines like:
#+cindex: @samp{_ALL} suffix, in properties
#+cindex: @samp{PROPERTY}, keyword
@@ -5087,7 +5091,8 @@ The following commands help to work with properties:
#+findex: org-insert-drawer
Insert a property drawer into the current entry. The drawer is
inserted early in the entry, but after the lines with planning
- information like deadlines.
+ information like deadlines. If before first headline the drawer is
+ inserted at the top of the drawer after any potential comments.
- {{{kbd(C-c C-c)}}} (~org-property-action~) ::
@@ -5310,11 +5315,6 @@ done by defining a column format line.
:DESCRIPTION: Where defined, where valid?
:END:
-To define a column format for an entire file, use a line like:
-
-#+cindex: @samp{COLUMNS}, keyword
-: #+COLUMNS: %25ITEM %TAGS %PRIORITY %TODO
-
To specify a format that only applies to a specific tree, add
a =COLUMNS= property to the top node of that tree, for example:
@@ -5325,6 +5325,13 @@ a =COLUMNS= property to the top node of that tree, for example:
:END:
#+end_example
+A =COLUMNS= property within a property drawer before first headline
+will apply to the entire file. As an addition to property drawers,
+keywords can also be defined for an entire file using a line like:
+
+#+cindex: @samp{COLUMNS}, keyword
+: #+COLUMNS: %25ITEM %TAGS %PRIORITY %TODO
+
If a =COLUMNS= property is present in an entry, it defines columns for
the entry itself, and for the entire subtree below it. Since the
column definition is part of the hierarchical structure of the
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index c9722aa..275a33b 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -10,6 +10,16 @@ See the end of the file for license conditions.
Please send Org bug reports to mailto:emacs-orgmode@gnu.org.
+* Version Next
+** New features
+*** Property drawers before first headline, outline level 0
+Property drawers will now work before first headline and Org mode is
+moving more towards making things before the first headline behave
+just as if it was at outline level 0. Inheritance for properties will
+work also for this level. In other words; defining things in a
+property drawer before the first headline will make them "inheritable"
+for all headlines.
+
* Version 9.3
** Incompatible changes
@@ -2216,7 +2226,7 @@ without changing the headline.
*** Hierarchies of tags
-The functionality of nesting tags in hierarchies is added to org-mode.
+The functionality of nesting tags in hierarchies is added to Org mode.
This is the generalization of what was previously called "Tag groups"
in the manual. That term is now changed to "Tag hierarchy".
diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index 090e632..a25016e 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -7992,29 +7992,26 @@ argument EXPAND can be used for the TYPE tag and will expand the
tags in the FILTER if any of the tags in FILTER are grouptags."
;; Deactivate `org-agenda-entry-text-mode' when filtering
(when org-agenda-entry-text-mode (org-agenda-entry-text-mode))
- (let (tags cat txt)
- (setq org-agenda-filter-form (org-agenda-filter-make-matcher
- filter type expand))
- ;; Only set `org-agenda-filtered-by-category' to t when a unique
- ;; category is used as the filter:
- (setq org-agenda-filtered-by-category
- (and (eq type 'category)
- (not (equal (substring (car filter) 0 1) "-"))))
- (org-agenda-set-mode-name)
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (if (org-get-at-bol 'org-hd-marker)
- (progn
- (setq tags (org-get-at-bol 'tags)
- cat (org-agenda-get-category)
- txt (org-get-at-bol 'txt))
- (unless (eval org-agenda-filter-form)
- (org-agenda-filter-hide-line type))
- (beginning-of-line 2))
- (beginning-of-line 2))))
- (when (get-char-property (point) 'invisible)
- (ignore-errors (org-agenda-previous-line)))))
+ (setq org-agenda-filter-form (org-agenda-filter-make-matcher
+ filter type expand))
+ ;; Only set `org-agenda-filtered-by-category' to t when a unique
+ ;; category is used as the filter:
+ (setq org-agenda-filtered-by-category
+ (and (eq type 'category)
+ (not (equal (substring (car filter) 0 1) "-"))))
+ (org-agenda-set-mode-name)
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (org-get-at-bol 'org-hd-marker)
+ (let ((tags (org-get-at-bol 'tags))
+ (cat (org-agenda-get-category))
+ (txt (org-get-at-bol 'txt)))
+ (unless (eval org-agenda-filter-form)
+ (org-agenda-filter-hide-line type))))
+ (beginning-of-line 2)))
+ (when (get-char-property (point) 'invisible)
+ (ignore-errors (org-agenda-previous-line))))
(defun org-agenda-filter-top-headline-apply (hl &optional negative)
"Filter by top headline HL."
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index bc49be7..27d06ec 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -258,7 +258,7 @@ Shows a list of commands and prompts for another key to execute a command."
(when marker
(set-buffer (marker-buffer marker))
(goto-char marker))
- (org-back-to-heading t)
+ (org-back-to-heading-or-point-min t)
(save-excursion
(save-window-excursion
(unless org-attach-expert
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 4f97e17..67343e0 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -1736,11 +1736,11 @@ The template may still contain \"%?\" for cursor positioning."
(_ (error "Invalid `org-capture--clipboards' value: %S"
org-capture--clipboards)))))
("p"
- ;; We remove file properties inherited from
+ ;; We remove keyword properties inherited from
;; target buffer so `org-read-property-value' has
;; a chance to find allowed values in sub-trees
;; from the target buffer.
- (setq-local org-file-properties nil)
+ (setq-local org-keyword-properties nil)
(let* ((origin (set-marker (make-marker)
(org-capture-get :pos)
(org-capture-get :buffer)))
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index 4446a16..c167ba9 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -556,6 +556,12 @@ use of this function is for the stuck project list."
(define-obsolete-function-alias 'org-make-link-regexps
'org-link-make-regexps "Org 9.3")
+(define-obsolete-function-alias 'org-file-properties
+ 'org-keyword-properties "Org 9.3")
+
+(define-obsolete-function-alias 'org-property-global-value
+ 'org-property-global-or-keyword-value "Org 9.3")
+
(define-obsolete-variable-alias 'org-angle-link-re
'org-link-angle-re "Org 9.3")
diff --git a/lisp/org-element.el b/lisp/org-element.el
index 56b3cc4..110ff56 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -3910,7 +3910,10 @@ element it has to parse."
;; LaTeX Environment.
((looking-at org-element--latex-begin-environment)
(org-element-latex-environment-parser limit affiliated))
- ;; Drawer and Property Drawer.
+ ;; Property drawer (before first headline, else it's catched above).
+ ((org-at-property-block-p)
+ (org-element-property-drawer-parser limit))
+ ;; Drawer.
((looking-at org-drawer-regexp)
(org-element-drawer-parser limit affiliated))
;; Fixed Width
diff --git a/lisp/org.el b/lisp/org.el
index 67c5d1b..d78321e 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -348,6 +348,9 @@ FULL is given."
;;; Syntax Constants
+;;;; Keyword
+(defconst org-keyword-regexp "^[ \t]*#\\+\\(\\S-+?\\):[ \t]*\\(.*\\)$"
+ "Regular expression for keyword-lines")
;;;; Block
@@ -3180,8 +3183,13 @@ This list will be combined with the constant `org-global-properties-fixed'.
The entries in this list are cons cells where the car is a property
name and cdr is a string with the value.
-You can set buffer-local values for the same purpose in the variable
-`org-file-properties' this by adding lines like
+Buffer local properties are added either by a document property drawer
+
+:PROPERTIES:
+:NAME: VALUE
+:END:
+
+or by adding lines like
#+PROPERTY: NAME VALUE"
:group 'org-properties
@@ -3189,10 +3197,14 @@ You can set buffer-local values for the same purpose in the variable
(cons (string :tag "Property")
(string :tag "Value"))))
-(defvar-local org-file-properties nil
+(defvar-local org-keyword-properties nil
"List of property/value pairs that can be inherited by any entry.
-Valid for the current buffer.
-This variable is populated from #+PROPERTY lines.")
+Valid for the current buffer. This variable is populated from
+#+PROPERTY lines.
+
+Note that properties are defined also in property drawers.
+Properties defined there will take precedence over properties
+defined as keywords.")
(defgroup org-agenda nil
"Options concerning agenda views in Org mode."
@@ -3201,11 +3213,18 @@ This variable is populated from #+PROPERTY lines.")
(defvar-local org-category nil
"Variable used by Org files to set a category for agenda display.
-Such files should use a file variable to set it, for example
+There are multiple ways to set the category. One way is to set
+it in the document property drawer. For example:
+
+:PROPERTIES:
+:CATEGORY: ELisp
+:END:
+
+Other ways to define it is as an emacs file variable, for example
# -*- mode: org; org-category: \"ELisp\"
-or contain a special line
+or for the file to contain a special line:
#+CATEGORY: ELisp
@@ -3973,14 +3992,13 @@ Here are a few examples:
Archive in file ./basement (relative path), as level 3 trees
below the level 2 heading \"** Finished Tasks\".
-You may set this option on a per-file basis by adding to the buffer a
-line like
+You may define it locally by setting an ARCHIVE property. If
+such a property is found in the file or in an entry, and anywhere
+up the hierarchy, it will be used.
-#+ARCHIVE: basement::** Finished Tasks
+You can also set it for the whole file using the keyword-syntax:
-You may also define it locally for a subtree by setting an ARCHIVE property
-in the entry. If such a property is found in an entry, or anywhere up
-the hierarchy, it will be used."
+#+ARCHIVE: basement::** Finished Tasks"
:group 'org-archive
:type 'string)
@@ -4338,8 +4356,8 @@ related expressions."
(setq org-tag-groups-alist
(org-tag-alist-to-groups org-current-tag-alist))
(unless tags-only
- ;; File properties.
- (setq-local org-file-properties (cdr (assq 'property alist)))
+ ;; Properties.
+ (setq-local org-keyword-properties (cdr (assq 'property alist)))
;; Archive location.
(let ((archive (cdr (assq 'archive alist))))
(when archive (setq-local org-archive-location archive)))
@@ -4347,9 +4365,9 @@ related expressions."
(let ((cat (org-string-nw-p (cdr (assq 'category alist)))))
(when cat
(setq-local org-category (intern cat))
- (setq-local org-file-properties
+ (setq-local org-keyword-properties
(org--update-property-plist
- "CATEGORY" cat org-file-properties))))
+ "CATEGORY" cat org-keyword-properties))))
;; Columns.
(let ((column (cdr (assq 'columns alist))))
(when column (setq-local org-columns-default-format column)))
@@ -8269,13 +8287,14 @@ the value of the drawer property."
(inhibit-read-only t)
(inherit? (org-property-inherit-p dprop))
(property-re (org-re-property (concat (regexp-quote dprop) "\\+?") t))
- (global (and inherit? (org--property-global-value dprop nil))))
+ (global-or-keyword (and inherit?
+ (org--property-global-or-keyword-value dprop nil))))
(with-silent-modifications
(org-with-point-at 1
- ;; Set global values (e.g., values defined through
- ;; "#+PROPERTY:" keywords) to the whole buffer.
- (when global (put-text-property (point-min) (point-max) tprop global))
- ;; Set local values.
+ ;; Set global and keyword based values to the whole buffer.
+ (when global-or-keyword
+ (put-text-property (point-min) (point-max) tprop global-or-keyword))
+ ;; Set values based on property-drawers throughout the document.
(while (re-search-forward property-re nil t)
(when (org-at-property-p)
(org-refresh-property tprop (org-entry-get (point) dprop) inherit?))
@@ -8283,21 +8302,29 @@ the value of the drawer property."
(defun org-refresh-property (tprop p &optional inherit)
"Refresh the buffer text property TPROP from the drawer property P.
-The refresh happens only for the current headline, or the whole
-sub-tree if optional argument INHERIT is non-nil."
- (unless (org-before-first-heading-p)
- (save-excursion
- (org-back-to-heading t)
- (let ((start (point))
- (end (save-excursion
- (if inherit (org-end-of-subtree t t)
- (or (outline-next-heading) (point-max))))))
- (if (symbolp tprop)
- ;; TPROP is a text property symbol.
- (put-text-property start end tprop p)
- ;; TPROP is an alist with (property . function) elements.
- (pcase-dolist (`(,prop . ,f) tprop)
- (put-text-property start end prop (funcall f p))))))))
+The refresh happens only for the current entry, or the whole
+sub-tree if optional argument INHERIT is non-nil.
+
+If point is before first headline, the function applies to the
+part before the first headline. In that particular case, when
+optional argument INHERIT is non-nil, it refreshes properties for
+the whole buffer."
+ (save-excursion
+ (org-back-to-heading-or-point-min t)
+ (let ((start (point))
+ (end (save-excursion
+ (cond ((and inherit (org-before-first-heading-p))
+ (point-max))
+ (inherit
+ (org-end-of-subtree t t))
+ ((outline-next-heading))
+ ((point-max))))))
+ (if (symbolp tprop)
+ ;; TPROP is a text property symbol.
+ (put-text-property start end tprop p)
+ ;; TPROP is an alist with (property . function) elements.
+ (pcase-dolist (`(,prop . ,f) tprop)
+ (put-text-property start end prop (funcall f p)))))))
(defun org-refresh-category-properties ()
"Refresh category text properties in the buffer."
@@ -8313,9 +8340,9 @@ sub-tree if optional argument INHERIT is non-nil."
(t org-category))))
(with-silent-modifications
(org-with-wide-buffer
- ;; Set buffer-wide category. Search last #+CATEGORY keyword.
- ;; This is the default category for the buffer. If none is
- ;; found, fall-back to `org-category' or buffer file name.
+ ;; Set buffer-wide property from keyword. Search last #+CATEGORY
+ ;; keyword. If none is found, fall-back to `org-category' or
+ ;; buffer file name, or set it by the document property drawer.
(put-text-property
(point-min) (point-max)
'org-category
@@ -8327,15 +8354,20 @@ sub-tree if optional argument INHERIT is non-nil."
(throw 'buffer-category
(org-element-property :value element)))))
default-category))
- ;; Set sub-tree specific categories.
+ ;; Set categories from the document property drawer or
+ ;; property drawers in the outline. If category is found in
+ ;; the property drawer for the whole buffer that value
+ ;; overrides the keyword-based value set above.
(goto-char (point-min))
(let ((regexp (org-re-property "CATEGORY")))
(while (re-search-forward regexp nil t)
(let ((value (match-string-no-properties 3)))
(when (org-at-property-p)
(put-text-property
- (save-excursion (org-back-to-heading t) (point))
- (save-excursion (org-end-of-subtree t t) (point))
+ (save-excursion (org-back-to-heading-or-point-min t))
+ (save-excursion (if (org-before-first-heading-p)
+ (point-max)
+ (org-end-of-subtree t t)))
'org-category
value)))))))))
@@ -12921,30 +12953,45 @@ Modifications are made by side-effect. Return new alist."
(defun org-get-property-block (&optional beg force)
"Return the (beg . end) range of the body of the property drawer.
-BEG is the beginning of the current subtree, or of the part
-before the first headline. If it is not given, it will be found.
-If the drawer does not exist, create it if FORCE is non-nil, or
-return nil."
+BEG is the beginning of the current subtree or the beginning of
+the document if before the first headline. If it is not given,
+it will be found. If the drawer does not exist, create it if
+FORCE is non-nil, or return nil."
(org-with-wide-buffer
- (when beg (goto-char beg))
- (unless (org-before-first-heading-p)
- (let ((beg (cond (beg)
+ (let ((beg (cond (beg (goto-char beg))
((or (not (featurep 'org-inlinetask))
(org-inlinetask-in-task-p))
- (org-back-to-heading t))
- (t (org-with-limited-levels (org-back-to-heading t))))))
- (forward-line)
- (when (looking-at-p org-planning-line-re) (forward-line))
- (cond ((looking-at org-property-drawer-re)
- (forward-line)
- (cons (point) (progn (goto-char (match-end 0))
- (line-beginning-position))))
- (force
- (goto-char beg)
- (org-insert-property-drawer)
- (let ((pos (save-excursion (search-forward ":END:")
- (line-beginning-position))))
- (cons pos pos))))))))
+ (org-back-to-heading-or-point-min t) (point))
+ (t (org-with-limited-levels
+ (org-back-to-heading-or-point-min t))
+ (point)))))
+ ;; Move point to its position according to its positional rules.
+ (cond ((org-before-first-heading-p)
+ (while (and (org-at-comment-p) (bolp)) (forward-line)))
+ (t (forward-line)
+ (when (looking-at-p org-planning-line-re) (forward-line))))
+ (cond ((looking-at org-property-drawer-re)
+ (forward-line)
+ (cons (point) (progn (goto-char (match-end 0))
+ (line-beginning-position))))
+ (force
+ (goto-char beg)
+ (org-insert-property-drawer)
+ (let ((pos (save-excursion (re-search-forward org-property-drawer-re)
+ (line-beginning-position))))
+ (cons pos pos)))))))
+
+(defun org-at-property-block-p ()
+ "Return t when point is at the first line of a property drawer.
+The property drawer is validated according to its positional
+rules using `org-get-property-block'."
+ (save-excursion
+ (beginning-of-line)
+ (and (looking-at org-property-start-re)
+ (forward-line)
+ (let ((property-drawer (org-get-property-block)))
+ (and property-drawer
+ (= (point) (car property-drawer)))))))
(defun org-at-property-p ()
"Non-nil when point is inside a property drawer.
@@ -13029,7 +13076,7 @@ Return value is an alist. Keys are properties, as upcased
strings."
(org-with-point-at pom
(when (and (derived-mode-p 'org-mode)
- (ignore-errors (org-back-to-heading t)))
+ (org-back-to-heading-or-point-min t))
(catch 'exit
(let* ((beg (point))
(specific (and (stringp which) (upcase which)))
@@ -13238,13 +13285,13 @@ unless LITERAL-NIL is non-nil."
;; Return final values.
(and (not (equal value '(nil))) (nreverse value))))))
-(defun org--property-global-value (property literal-nil)
- "Return value for PROPERTY in current buffer.
+(defun org--property-global-or-keyword-value (property literal-nil)
+ "Return value for PROPERTY as defined by global properties or by keyword.
Return value is a string. Return nil if property is not set
-globally. Also return nil when PROPERTY is set to \"nil\",
-unless LITERAL-NIL is non-nil."
+globally or by keyword. Also return nil when PROPERTY is set to
+\"nil\", unless LITERAL-NIL is non-nil."
(let ((global
- (cdr (or (assoc-string property org-file-properties t)
+ (cdr (or (assoc-string property org-keyword-properties t)
(assoc-string property org-global-properties t)
(assoc-string property org-global-properties-fixed t)))))
(if literal-nil global (org-not-nil global))))
@@ -13393,12 +13440,12 @@ However, if LITERAL-NIL is set, return the string value \"nil\" instead."
value)))
(cond
((car v)
- (org-back-to-heading t)
+ (org-back-to-heading-or-point-min t)
(move-marker org-entry-property-inherited-from (point))
(throw 'exit nil))
- ((org-up-heading-safe))
+ ((org-up-heading-or-point-min))
(t
- (let ((global (org--property-global-value property literal-nil)))
+ (let ((global (org--property-global-or-keyword-value property literal-nil)))
(cond ((not global))
(value (setq value (concat global " " value)))
(t (setq value global))))
@@ -13430,8 +13477,8 @@ decreases scheduled or deadline date by one day."
(user-error "Invalid property name: \"%s\"" property)))
(org-with-point-at pom
(if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p))
- (org-back-to-heading t)
- (org-with-limited-levels (org-back-to-heading t)))
+ (org-back-to-heading-or-point-min t)
+ (org-with-limited-levels (org-back-to-heading-or-point-min t)))
(let ((beg (point)))
(cond
((equal property "TODO")
@@ -13567,21 +13614,26 @@ COLUMN formats in the current buffer."
Do nothing if the drawer already exists. The newly created
drawer is immediately hidden."
(org-with-wide-buffer
+ ;; Set point to the position where the drawer should be inserted.
(if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p))
- (org-back-to-heading t)
- (org-with-limited-levels (org-back-to-heading t)))
- (forward-line)
- (when (looking-at-p org-planning-line-re) (forward-line))
+ (org-back-to-heading-or-point-min t)
+ (org-with-limited-levels (org-back-to-heading-or-point-min t)))
+ (if (org-before-first-heading-p)
+ (while (and (org-at-comment-p) (bolp)) (forward-line))
+ (progn
+ (forward-line)
+ (when (looking-at-p org-planning-line-re) (forward-line))))
(unless (looking-at-p org-property-drawer-re)
;; Make sure we start editing a line from current entry, not from
;; next one. It prevents extending text properties or overlays
;; belonging to the latter.
- (when (bolp) (backward-char))
- (let ((begin (1+ (point)))
+ (when (and (bolp) (> (point) (point-min))) (backward-char))
+ (let ((begin (if (bobp) (point) (1+ (point))))
(inhibit-read-only t))
- (insert "\n:PROPERTIES:\n:END:")
+ (unless (bobp) (insert "\n"))
+ (insert ":PROPERTIES:\n:END:")
(org-flag-drawer t nil (line-end-position 0) (point))
- (when (eobp) (insert "\n"))
+ (when (or (eobp) (= begin (point-min))) (insert "\n"))
(org-indent-region begin (point))))))
(defun org-insert-drawer (&optional arg drawer)
@@ -20488,6 +20540,15 @@ interactive command with similar behavior."
(error (error "Before first headline at position %d in buffer %s"
(point) (current-buffer)))))
+(defun org-back-to-heading-or-point-min (&optional invisible-ok)
+ "Go back to heading or first point in buffer.
+If point is before first heading go to first point in buffer
+instead of back to heading."
+ (condition-case nil
+ (outline-back-to-heading invisible-ok)
+ (error
+ (goto-char (point-min)))))
+
(defun org-before-first-heading-p ()
"Before first heading?"
(org-with-limited-levels
@@ -20521,6 +20582,12 @@ unless optional argument NO-INHERITANCE is non-nil."
(beginning-of-line)
(looking-at "^[ \t]*# "))))
+(defun org-at-keyword-p nil
+ "Return t if cursor is at a keyword-line."
+ (save-excursion
+ (move-beginning-of-line 1)
+ (looking-at org-keyword-regexp)))
+
(defun org-at-drawer-p nil
"Return t if cursor is at a drawer keyword."
(save-excursion
@@ -20568,6 +20635,17 @@ make a significant difference in outlines with very many siblings."
(re-search-backward (format "^\\*\\{1,%d\\} " level-up) nil t)
(funcall outline-level)))))
+(defun org-up-heading-or-point-min ()
+ "Move to the heading line of which the present is a subheading, or point-min.
+This version is needed to make point-min behave like a virtual
+heading of level 0 for property-inheritance. It will return the
+level of the headline found (down to 0) or nil if already at a
+point before the first headline or at point-min."
+ (when (ignore-errors (org-back-to-heading t))
+ (if (< 1 (funcall outline-level))
+ (org-up-heading-safe)
+ (unless (= (point) (point-min)) (goto-char (point-min))))))
+
(defun org-first-sibling-p ()
"Is this heading the first child of its parents?"
(interactive)
@@ -20668,28 +20746,31 @@ If there is no such heading, return nil."
(defun org-end-of-subtree (&optional invisible-ok to-heading)
"Goto to the end of a subtree."
;; This contains an exact copy of the original function, but it uses
- ;; `org-back-to-heading', to make it work also in invisible
- ;; trees. And is uses an invisible-ok argument.
+ ;; `org-back-to-heading-or-point-min', to make it work also in invisible
+ ;; trees and before first headline. And is uses an invisible-ok argument.
;; Under Emacs this is not needed, but the old outline.el needs this fix.
;; Furthermore, when used inside Org, finding the end of a large subtree
;; with many children and grandchildren etc, this can be much faster
;; than the outline version.
- (org-back-to-heading invisible-ok)
+ (org-back-to-heading-or-point-min invisible-ok)
(let ((first t)
(level (funcall outline-level)))
- (if (and (derived-mode-p 'org-mode) (< level 1000))
- ;; A true heading (not a plain list item), in Org
- ;; This means we can easily find the end by looking
- ;; only for the right number of stars. Using a regexp to do
- ;; this is so much faster than using a Lisp loop.
- (let ((re (concat "^\\*\\{1," (int-to-string level) "\\} ")))
- (forward-char 1)
- (and (re-search-forward re nil 'move) (beginning-of-line 1)))
- ;; something else, do it the slow way
- (while (and (not (eobp))
- (or first (> (funcall outline-level) level)))
- (setq first nil)
- (outline-next-heading)))
+ (cond ((= level 0)
+ (goto-char (point-max)))
+ ((and (derived-mode-p 'org-mode) (< level 1000))
+ ;; A true heading (not a plain list item), in Org
+ ;; This means we can easily find the end by looking
+ ;; only for the right number of stars. Using a regexp to do
+ ;; this is so much faster than using a Lisp loop.
+ (let ((re (concat "^\\*\\{1," (int-to-string level) "\\} ")))
+ (forward-char 1)
+ (and (re-search-forward re nil 'move) (beginning-of-line 1))))
+ (t
+ ;; something else, do it the slow way
+ (while (and (not (eobp))
+ (or first (> (funcall outline-level) level)))
+ (setq first nil)
+ (outline-next-heading))))
(unless to-heading
(when (memq (preceding-char) '(?\n ?\^M))
;; Go to end of line before heading
diff --git a/testing/examples/property-inheritance.org b/testing/examples/property-inheritance.org
index 6979de5..9c0b7e6 100644
--- a/testing/examples/property-inheritance.org
+++ b/testing/examples/property-inheritance.org
@@ -1,5 +1,7 @@
-#+property: header-args :var foo=1
-#+property: header-args+ :var bar=2
+:PROPERTIES:
+:header-args: :var foo=1
+:header-args+: :var bar=2
+:END:
#+begin_src emacs-lisp
(+ foo bar)
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index f2ab380..01c1344 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -1925,6 +1925,15 @@ e^{i\\pi}+1=0
(let ((element (org-element-at-point)))
(list (org-element-property :key element)
(org-element-property :value element))))))
+ ;; The insides of property blocks on document level are parsed the
+ ;; same way as headline property blocks. I.e. the concept of
+ ;; `node-property' apply also for properties in those blocks.
+ (should
+ (equal '("abc" "value")
+ (org-test-with-temp-text ":PROPERTIES:\n<point>:abc: value\n:END:"
+ (let ((element (org-element-at-point)))
+ (list (org-element-property :key element)
+ (org-element-property :value element))))))
;; Value should be trimmed.
(should
(equal "value"
@@ -2111,6 +2120,18 @@ Outside list"
(org-test-with-temp-text
"* H\nDEADLINE: <2014-03-04 tue.>\n<point>:PROPERTIES:\n:prop: value\n:END:"
(org-element-type (org-element-at-point)))))
+ (should
+ (eq 'property-drawer
+ (org-test-with-temp-text "<point>:PROPERTIES:\n:prop: value\n:END:"
+ (org-element-type (org-element-at-point)))))
+ (should
+ (eq 'property-drawer
+ (org-test-with-temp-text "# C\n# C\n<point>:PROPERTIES:\n:prop: value\n:END:"
+ (org-element-type (org-element-at-point)))))
+ (should-not
+ (eq 'property-drawer
+ (org-test-with-temp-text "\n<point>:PROPERTIES:\n:prop: value\n:END:"
+ (org-element-type (org-element-at-point)))))
;; Allow properties without value and no property at all.
(should
(eq 'property-drawer
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 29c6ed7..fcf41ac 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -386,10 +386,118 @@
;;; Drawers
+(ert-deftest test-org/at-property-p ()
+ "Test `org-at-property-p' specifications."
+ (should
+ (equal 't
+ (org-test-with-temp-text "* H\n:PROPERTIES:\n<point>:PROP: t\n:END:\n"
+ (org-at-property-p))))
+ (should
+ (equal 't
+ (org-test-with-temp-text ":PROPERTIES:\n<point>:PROP: t\n:END:\n"
+ (org-at-property-p)))))
+
+(ert-deftest test-org/at-property-block-p ()
+ "Test `org-at-property-block-p' specifications."
+ (should
+ (equal 't
+ (org-test-with-temp-text "* H\n<point>:PROPERTIES:\n:PROP: t\n:END:\n"
+ (org-at-property-block-p))))
+ (should
+ (equal 't
+ (org-test-with-temp-text ":PROPERTIES:\n:PROP: t\n:END:\n"
+ (org-at-property-block-p))))
+ ;; The function only returns t if point is at the first line of a
+ ;; property block.
+ (should-not
+ (equal 't
+ (org-test-with-temp-text ":PROPERTIES:\n<point>:PROP: t\n:END:\n"
+ (org-at-property-block-p)))))
+
+(ert-deftest test-org/get-property-block ()
+ "Test `org-get-property-block' specifications."
+ (should
+ (equal '(14 . 14)
+ (org-test-with-temp-text ":PROPERTIES:\n:END:\n* H\n"
+ (org-get-property-block))))
+ (should
+ (equal '(14 . 14)
+ (org-test-with-temp-text ":PROPERTIES:\n:END:\n"
+ (org-get-property-block))))
+ ;; Comments above a document property block is ok.
+ (should
+ (equal '(18 . 18)
+ (org-test-with-temp-text "# C\n:PROPERTIES:\n:END:\n"
+ (org-get-property-block))))
+ ;; Keywords above a document property block is ok.
+ (should
+ (equal '(22 . 22)
+ (org-test-with-temp-text "# C\n# C\n:PROPERTIES:\n:END:\n"
+ (org-get-property-block))))
+ ;; Comments and keywords are allowed before a document property block.
+ (should
+ (equal '(18 . 27)
+ (org-test-with-temp-text "# C\n:PROPERTIES:\n:KEY: V:\n:END:\n"
+ (org-get-property-block))))
+ ;; A document property block will not be valid if there are lines
+ ;; with whitespace above it
+ (should-not
+ (org-test-with-temp-text "\n:PROPERTIES:\n:END:\n"
+ (org-get-property-block)))
+ (should
+ (equal '(18 . 18)
+ (org-test-with-temp-text "* H\n:PROPERTIES:\n:END:\n<point>"
+ (org-get-property-block))))
+ (should
+ (equal "* H\n:PROPERTIES:\n:END:\n"
+ (org-test-with-temp-text "* H"
+ (let ((org-adapt-indentation nil))
+ (org-get-property-block nil 'force))
+ (buffer-string))))
+ (should
+ (equal ":PROPERTIES:\n:END:\n"
+ (org-test-with-temp-text ""
+ (org-get-property-block nil 'force)
+ (buffer-string))))
+ (should
+ (equal "* H1\n :PROPERTIES:\n :END:\n* H2"
+ (org-test-with-temp-text "* H1\n* H2"
+ (let ((org-adapt-indentation t))
+ (org-get-property-block nil 'force))
+ (buffer-string)))))
+
(ert-deftest test-org/insert-property-drawer ()
"Test `org-insert-property-drawer' specifications."
- ;; Error before first headline.
- (should-error (org-test-with-temp-text "" (org-insert-property-drawer)))
+ ;; Insert drawer in empty buffer
+ (should
+ (equal ":PROPERTIES:\n:END:\n"
+ (org-test-with-temp-text ""
+ (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+ (buffer-string))))
+ ;; Insert drawer in document header with existing comment and
+ ;; keyword.
+ (should
+ (equal "# C\n:PROPERTIES:\n:END:\n#+TITLE: T"
+ (org-test-with-temp-text "# C\n#+TITLE: T"
+ (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+ (buffer-string))))
+ ;; Insert drawer in document header with existing keyword.
+ (should
+ (equal ":PROPERTIES:\n:END:\n#+TITLE: T"
+ (org-test-with-temp-text "#+TITLE: T"
+ (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+ (buffer-string))))
+ (should
+ (equal ":PROPERTIES:\n:END:"
+ (org-test-with-temp-text ":PROPERTIES:\n:END:"
+ (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+ (buffer-string))))
+ ;; Insert drawer in document header with one existing heading in buffer.
+ (should
+ (equal ":PROPERTIES:\n:END:\n\n* T\n"
+ (org-test-with-temp-text "\n* T\n"
+ (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+ (buffer-string))))
;; Insert drawer right after headline if there is no planning line,
;; or after it otherwise.
(should
@@ -2178,19 +2286,19 @@ SCHEDULED: <2014-03-04 tue.>"
(equal "foo=1"
(org-test-with-temp-text "#+PROPERTY: var foo=1"
(org-mode-restart)
- (cdr (assoc "var" org-file-properties)))))
+ (cdr (assoc "var" org-keyword-properties)))))
(should
(equal
"foo=1 bar=2"
(org-test-with-temp-text "#+PROPERTY: var foo=1\n#+PROPERTY: var+ bar=2"
(org-mode-restart)
- (cdr (assoc "var" org-file-properties)))))
+ (cdr (assoc "var" org-keyword-properties)))))
(should
(equal
"foo=1 bar=2"
(org-test-with-temp-text "#+PROPERTY: var foo=1\n#+PROPERTY: VAR+ bar=2"
(org-mode-restart)
- (cdr (assoc "var" org-file-properties)))))
+ (cdr (assoc "var" org-keyword-properties)))))
;; ARCHIVE keyword.
(should
(equal "%s_done::"
@@ -2207,7 +2315,7 @@ SCHEDULED: <2014-03-04 tue.>"
(equal "test"
(org-test-with-temp-text "#+CATEGORY: test"
(org-mode-restart)
- (cdr (assoc "CATEGORY" org-file-properties)))))
+ (cdr (assoc "CATEGORY" org-keyword-properties)))))
;; COLUMNS keyword.
(should
(equal "%25ITEM %TAGS %PRIORITY %TODO"
@@ -2291,7 +2399,7 @@ SCHEDULED: <2014-03-04 tue.>"
(org-test-with-temp-text
(format "#+SETUPFILE: \"%s/examples/setupfile.org\"" org-test-dir)
(org-mode-restart)
- (cdr (assoc "a" org-file-properties))))))
+ (cdr (assoc "a" org-keyword-properties))))))
@@ -4941,6 +5049,66 @@ Paragraph<point>"
(org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:A+: 2\n:END:"
(org-property-values "A")))))
+(ert-deftest test-org/set-property ()
+ "Test `org-set-property' specifications."
+ (should
+ (equal
+ ":PROPERTIES:\n:TEST: t\n:END:\n"
+ (org-test-with-temp-text ""
+ (let ((org-property-format "%s %s"))
+ (org-set-property "TEST" "t"))
+ (buffer-string))))
+ (should
+ (equal
+ "* H\n:PROPERTIES:\n:TEST: t\n:END:\n"
+ (org-test-with-temp-text "* H"
+ (let ((org-adapt-indentation nil)
+ (org-property-format "%s %s"))
+ (org-set-property "TEST" "t"))
+ (buffer-string)))))
+
+(ert-deftest test-org/delete-property ()
+ "Test `org-delete-property' specifications."
+ (should
+ (equal
+ ""
+ (org-test-with-temp-text ":PROPERTIES:\n:TEST: t\n:END:\n"
+ (org-delete-property "TEST")
+ (buffer-string))))
+ (should
+ (equal
+ ":PROPERTIES:\n:TEST1: t\n:END:\n"
+ (org-test-with-temp-text ":PROPERTIES:\n:TEST1: t\n:TEST2: t\n:END:\n"
+ (org-delete-property "TEST2")
+ (buffer-string))))
+ (should
+ (equal
+ "* H\n"
+ (org-test-with-temp-text "* H\n:PROPERTIES:\n:TEST: t\n:END:\n"
+ (org-delete-property "TEST")
+ (buffer-string))))
+ (should
+ (equal
+ "* H\n:PROPERTIES:\n:TEST1: t\n:END:\n"
+ (org-test-with-temp-text "* H\n:PROPERTIES:\n:TEST1: t\n:TEST2: t\n:END:\n"
+ (org-delete-property "TEST2")
+ (buffer-string)))))
+
+(ert-deftest test-org/delete-property-globally ()
+ "Test `org-delete-property-global' specifications."
+ (should
+ (equal
+ ""
+ (org-test-with-temp-text ":PROPERTIES:\n:TEST: t\n:END:\n"
+ (org-delete-property-globally "TEST")
+ (buffer-string))))
+ (should
+ (equal
+ "* H\n"
+ (org-test-with-temp-text ":PROPERTIES:\n:TEST: t\n:END:\n* H\n:PROPERTIES:\n:TEST: nil\n:END:"
+ (org-delete-property-globally "TEST")
+ (buffer-string)))))
+
(ert-deftest test-org/find-property ()
"Test `org-find-property' specifications."
;; Regular test.
@@ -5024,6 +5192,10 @@ Paragraph<point>"
;; Regular test.
(should
(equal "1"
+ (org-test-with-temp-text ":PROPERTIES:\n:A: 1\n:END:"
+ (org-entry-get (point) "A"))))
+ (should
+ (equal "1"
(org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:"
(org-entry-get (point) "A"))))
;; Ignore case.
@@ -5064,6 +5236,11 @@ Paragraph<point>"
(should
(equal
"1"
+ (org-test-with-temp-text ":PROPERTIES:\n:A: 1\n:END:\n* H"
+ (org-entry-get (point-max) "A" t))))
+ (should
+ (equal
+ "1"
(org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2"
(org-entry-get (point-max) "A" t))))
(should
@@ -5080,9 +5257,27 @@ Paragraph<point>"
(equal
"1 2"
(org-test-with-temp-text
+ ":PROPERTIES:\n:A: 1\n:END:\n* H\n:PROPERTIES:\n:A+: 2\n:END:"
+ (org-entry-get (point-max) "A" t))))
+ (should
+ (equal
+ "1 2"
+ (org-test-with-temp-text
"* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2\n:PROPERTIES:\n:A+: 2\n:END:"
(org-entry-get (point-max) "A" t))))
(should
+ (equal
+ "1 2"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:A: 1\n:END:\n* H1\n* H2\n:PROPERTIES:\n:A+: 2\n:END:"
+ (org-entry-get (point-max) "A" t))))
+ (should
+ (equal
+ "1 2"
+ (org-test-with-temp-text
+ "* H1\n:PROPERTIES:\n:A: 1\n:END:\n* H2.1\n* H2.2\n:PROPERTIES:\n:A+: 2\n:END:"
+ (org-entry-get (point-max) "A" t))))
+ (should
(equal "1"
(org-test-with-temp-text
"#+PROPERTY: A 0\n* H\n:PROPERTIES:\n:A: 1\n:END:"
@@ -5093,6 +5288,14 @@ Paragraph<point>"
(org-test-with-temp-text
"#+PROPERTY: A 0\n* H\n:PROPERTIES:\n:A+: 1\n:END:"
(org-mode-restart)
+ (org-entry-get (point-max) "A" t))))
+ ;; document level property-drawer has precedance over
+ ;; global-property by PROPERTY-keyword.
+ (should
+ (equal "0 2"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:A: 0\n:END:\n#+PROPERTY: A 1\n* H\n:PROPERTIES:\n:A+: 2\n:END:"
+ (org-mode-restart)
(org-entry-get (point-max) "A" t)))))
(ert-deftest test-org/entry-properties ()
@@ -5416,8 +5619,44 @@ Paragraph<point>"
(let ((org-use-property-inheritance t))
(org-refresh-properties "A" 'org-test))
(get-text-property (point) 'org-test))))
+ ;; When a document level property-drawer is used, those properties
+ ;; should work exactly like headline-properties as if at a
+ ;; headline-level 0.
+ (should
+ (equal "1"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:A: 1\n:END:\n"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance t))
+ (org-refresh-properties "A" 'org-test))
+ (get-text-property (point) 'org-test))))
+ (should-not
+ (equal "1"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:A: 1\n:END:\n<point>* H1"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance nil))
+ (org-refresh-properties "A" 'org-test))
+ (get-text-property (point) 'org-test))))
+ (should
+ (equal "1"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:A: 1\n:END:\n<point>* H1"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance t))
+ (org-refresh-properties "A" 'org-test))
+ (get-text-property (point) 'org-test))))
+ (should
+ (equal "2"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:A: 1\n:END:\n<point>* H1\n:PROPERTIES:\n:A: 2\n:END:"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance t))
+ (org-refresh-properties "A" 'org-test))
+ (get-text-property (point) 'org-test))))
;; When property is inherited, use global value across the whole
- ;; buffer. However local values have precedence.
+ ;; buffer. However local values have precedence, as well as the
+ ;; document level property-drawer.
(should-not
(equal "1"
(org-test-with-temp-text "#+PROPERTY: A 1\n<point>* H1"
@@ -5439,8 +5678,60 @@ Paragraph<point>"
(org-mode-restart)
(let ((org-use-property-inheritance t))
(org-refresh-properties "A" 'org-test))
+ (get-text-property (point) 'org-test))))
+ ;; When both keyword-property and document-level property-block is
+ ;; defined, the property-block has precedance.
+ (should
+ (equal "1"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:A: 1\n:END:\n#+PROPERTY: A 2\n<point>* H1"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance t))
+ (org-refresh-properties "A" 'org-test))
(get-text-property (point) 'org-test)))))
+(ert-deftest test-org/refresh-category-properties ()
+ "Test `org-refresh-category-properties' specifications"
+ (should
+ (equal "cat1"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:CATEGORY: cat1\n:END:"
+ (org-refresh-category-properties)
+ (get-text-property (point) 'org-category))))
+ (should
+ (equal "cat1"
+ (org-test-with-temp-text
+ "* H\n:PROPERTIES:\n:CATEGORY: cat1\n:END:"
+ (org-refresh-category-properties)
+ (get-text-property (point) 'org-category))))
+ ;; Even though property-inheritance is deactivated, category
+ ;; property should be inherited. As described in
+ ;; `org-use-property-inheritance'.
+ (should
+ (equal "cat1"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:CATEGORY: cat1\n:END:\n<point>* H"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance nil))
+ (org-refresh-category-properties))
+ (get-text-property (point) 'org-category))))
+ (should
+ (equal "cat1"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:CATEGORY: cat1\n:END:\n<point>* H"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance t))
+ (org-refresh-category-properties))
+ (get-text-property (point) 'org-category))))
+ (should
+ (equal "cat2"
+ (org-test-with-temp-text
+ ":PROPERTIES:\n:CATEGORY: cat1\n:END:\n<point>* H\n:PROPERTIES:\n:CATEGORY: cat2\n:END:\n"
+ (org-mode-restart)
+ (let ((org-use-property-inheritance t))
+ (org-refresh-category-properties))
+ (get-text-property (point) 'org-category)))))
+
;;; Refile